mirror of
https://github.com/matrix-org/dendrite.git
synced 2024-11-26 00:01:55 -06:00
Update version of gomatrixserverlib (#111)
This commit is contained in:
parent
9d4d18ae7f
commit
aa179d451c
8
vendor/manifest
vendored
8
vendor/manifest
vendored
|
@ -89,10 +89,16 @@
|
|||
"revision": "193b8f88e381d12f2d53023fba25e43fc81dc5ac",
|
||||
"branch": "master"
|
||||
},
|
||||
{
|
||||
"importpath": "github.com/matrix-org/gomatrix",
|
||||
"repository": "https://github.com/matrix-org/gomatrix",
|
||||
"revision": "27ae91d1b3a3be1d83864c6c4dc38c2297f11aae",
|
||||
"branch": "master"
|
||||
},
|
||||
{
|
||||
"importpath": "github.com/matrix-org/gomatrixserverlib",
|
||||
"repository": "https://github.com/matrix-org/gomatrixserverlib",
|
||||
"revision": "9c075cb699ceb64cacd9136a0e7a0e0ff3dc9bcf",
|
||||
"revision": "8e7504f3b62366e621525c962314f53c045512e3",
|
||||
"branch": "master"
|
||||
},
|
||||
{
|
||||
|
|
201
vendor/src/github.com/matrix-org/gomatrix/LICENSE
vendored
Normal file
201
vendor/src/github.com/matrix-org/gomatrix/LICENSE
vendored
Normal file
|
@ -0,0 +1,201 @@
|
|||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright {yyyy} {name of copyright owner}
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
6
vendor/src/github.com/matrix-org/gomatrix/README.md
vendored
Normal file
6
vendor/src/github.com/matrix-org/gomatrix/README.md
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
# gomatrix
|
||||
[![GoDoc](https://godoc.org/github.com/matrix-org/gomatrix?status.svg)](https://godoc.org/github.com/matrix-org/gomatrix)
|
||||
|
||||
A Golang Matrix client.
|
||||
|
||||
**THIS IS UNDER ACTIVE DEVELOPMENT: BREAKING CHANGES ARE FREQUENT.**
|
681
vendor/src/github.com/matrix-org/gomatrix/client.go
vendored
Normal file
681
vendor/src/github.com/matrix-org/gomatrix/client.go
vendored
Normal file
|
@ -0,0 +1,681 @@
|
|||
// Package gomatrix implements the Matrix Client-Server API.
|
||||
//
|
||||
// Specification can be found at http://matrix.org/docs/spec/client_server/r0.2.0.html
|
||||
package gomatrix
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Client represents a Matrix client.
|
||||
type Client struct {
|
||||
HomeserverURL *url.URL // The base homeserver URL
|
||||
Prefix string // The API prefix eg '/_matrix/client/r0'
|
||||
UserID string // The user ID of the client. Used for forming HTTP paths which use the client's user ID.
|
||||
AccessToken string // The access_token for the client.
|
||||
Client *http.Client // The underlying HTTP client which will be used to make HTTP requests.
|
||||
Syncer Syncer // The thing which can process /sync responses
|
||||
Store Storer // The thing which can store rooms/tokens/ids
|
||||
|
||||
// The ?user_id= query parameter for application services. This must be set *prior* to calling a method. If this is empty,
|
||||
// no user_id parameter will be sent.
|
||||
// See http://matrix.org/docs/spec/application_service/unstable.html#identity-assertion
|
||||
AppServiceUserID string
|
||||
|
||||
syncingMutex sync.Mutex // protects syncingID
|
||||
syncingID uint32 // Identifies the current Sync. Only one Sync can be active at any given time.
|
||||
}
|
||||
|
||||
// HTTPError An HTTP Error response, which may wrap an underlying native Go Error.
|
||||
type HTTPError struct {
|
||||
WrappedError error
|
||||
Message string
|
||||
Code int
|
||||
}
|
||||
|
||||
func (e HTTPError) Error() string {
|
||||
var wrappedErrMsg string
|
||||
if e.WrappedError != nil {
|
||||
wrappedErrMsg = e.WrappedError.Error()
|
||||
}
|
||||
return fmt.Sprintf("msg=%s code=%d wrapped=%s", e.Message, e.Code, wrappedErrMsg)
|
||||
}
|
||||
|
||||
// BuildURL builds a URL with the Client's homserver/prefix/access_token set already.
|
||||
func (cli *Client) BuildURL(urlPath ...string) string {
|
||||
ps := []string{cli.Prefix}
|
||||
for _, p := range urlPath {
|
||||
ps = append(ps, p)
|
||||
}
|
||||
return cli.BuildBaseURL(ps...)
|
||||
}
|
||||
|
||||
// BuildBaseURL builds a URL with the Client's homeserver/access_token set already. You must
|
||||
// supply the prefix in the path.
|
||||
func (cli *Client) BuildBaseURL(urlPath ...string) string {
|
||||
// copy the URL. Purposefully ignore error as the input is from a valid URL already
|
||||
hsURL, _ := url.Parse(cli.HomeserverURL.String())
|
||||
parts := []string{hsURL.Path}
|
||||
parts = append(parts, urlPath...)
|
||||
hsURL.Path = path.Join(parts...)
|
||||
query := hsURL.Query()
|
||||
if cli.AccessToken != "" {
|
||||
query.Set("access_token", cli.AccessToken)
|
||||
}
|
||||
if cli.AppServiceUserID != "" {
|
||||
query.Set("user_id", cli.AppServiceUserID)
|
||||
}
|
||||
hsURL.RawQuery = query.Encode()
|
||||
return hsURL.String()
|
||||
}
|
||||
|
||||
// BuildURLWithQuery builds a URL with query paramters in addition to the Client's homeserver/prefix/access_token set already.
|
||||
func (cli *Client) BuildURLWithQuery(urlPath []string, urlQuery map[string]string) string {
|
||||
u, _ := url.Parse(cli.BuildURL(urlPath...))
|
||||
q := u.Query()
|
||||
for k, v := range urlQuery {
|
||||
q.Set(k, v)
|
||||
}
|
||||
u.RawQuery = q.Encode()
|
||||
return u.String()
|
||||
}
|
||||
|
||||
// SetCredentials sets the user ID and access token on this client instance.
|
||||
func (cli *Client) SetCredentials(userID, accessToken string) {
|
||||
cli.AccessToken = accessToken
|
||||
cli.UserID = userID
|
||||
}
|
||||
|
||||
// ClearCredentials removes the user ID and access token on this client instance.
|
||||
func (cli *Client) ClearCredentials() {
|
||||
cli.AccessToken = ""
|
||||
cli.UserID = ""
|
||||
}
|
||||
|
||||
// Sync starts syncing with the provided Homeserver. If Sync() is called twice then the first sync will be stopped and the
|
||||
// error will be nil.
|
||||
//
|
||||
// This function will block until a fatal /sync error occurs, so it should almost always be started as a new goroutine.
|
||||
// Fatal sync errors can be caused by:
|
||||
// - The failure to create a filter.
|
||||
// - Client.Syncer.OnFailedSync returning an error in response to a failed sync.
|
||||
// - Client.Syncer.ProcessResponse returning an error.
|
||||
// If you wish to continue retrying in spite of these fatal errors, call Sync() again.
|
||||
func (cli *Client) Sync() error {
|
||||
// Mark the client as syncing.
|
||||
// We will keep syncing until the syncing state changes. Either because
|
||||
// Sync is called or StopSync is called.
|
||||
syncingID := cli.incrementSyncingID()
|
||||
nextBatch := cli.Store.LoadNextBatch(cli.UserID)
|
||||
filterID := cli.Store.LoadFilterID(cli.UserID)
|
||||
if filterID == "" {
|
||||
filterJSON := cli.Syncer.GetFilterJSON(cli.UserID)
|
||||
resFilter, err := cli.CreateFilter(filterJSON)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
filterID = resFilter.FilterID
|
||||
cli.Store.SaveFilterID(cli.UserID, filterID)
|
||||
}
|
||||
|
||||
for {
|
||||
resSync, err := cli.SyncRequest(30000, nextBatch, filterID, false, "")
|
||||
if err != nil {
|
||||
duration, err2 := cli.Syncer.OnFailedSync(resSync, err)
|
||||
if err2 != nil {
|
||||
return err2
|
||||
}
|
||||
time.Sleep(duration)
|
||||
continue
|
||||
}
|
||||
|
||||
// Check that the syncing state hasn't changed
|
||||
// Either because we've stopped syncing or another sync has been started.
|
||||
// We discard the response from our sync.
|
||||
if cli.getSyncingID() != syncingID {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Save the token now *before* processing it. This means it's possible
|
||||
// to not process some events, but it means that we won't get constantly stuck processing
|
||||
// a malformed/buggy event which keeps making us panic.
|
||||
cli.Store.SaveNextBatch(cli.UserID, resSync.NextBatch)
|
||||
if err = cli.Syncer.ProcessResponse(resSync, nextBatch); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
nextBatch = resSync.NextBatch
|
||||
}
|
||||
}
|
||||
|
||||
func (cli *Client) incrementSyncingID() uint32 {
|
||||
cli.syncingMutex.Lock()
|
||||
defer cli.syncingMutex.Unlock()
|
||||
cli.syncingID++
|
||||
return cli.syncingID
|
||||
}
|
||||
|
||||
func (cli *Client) getSyncingID() uint32 {
|
||||
cli.syncingMutex.Lock()
|
||||
defer cli.syncingMutex.Unlock()
|
||||
return cli.syncingID
|
||||
}
|
||||
|
||||
// StopSync stops the ongoing sync started by Sync.
|
||||
func (cli *Client) StopSync() {
|
||||
// Advance the syncing state so that any running Syncs will terminate.
|
||||
cli.incrementSyncingID()
|
||||
}
|
||||
|
||||
// MakeRequest makes a JSON HTTP request to the given URL.
|
||||
// If "resBody" is not nil, the response body will be json.Unmarshalled into it.
|
||||
//
|
||||
// Returns the HTTP body as bytes on 2xx with a nil error. Returns an error if the response is not 2xx along
|
||||
// with the HTTP body bytes if it got that far. This error is an HTTPError which includes the returned
|
||||
// HTTP status code and possibly a RespError as the WrappedError, if the HTTP body could be decoded as a RespError.
|
||||
func (cli *Client) MakeRequest(method string, httpURL string, reqBody interface{}, resBody interface{}) ([]byte, error) {
|
||||
var req *http.Request
|
||||
var err error
|
||||
if reqBody != nil {
|
||||
var jsonStr []byte
|
||||
jsonStr, err = json.Marshal(reqBody)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req, err = http.NewRequest(method, httpURL, bytes.NewBuffer(jsonStr))
|
||||
} else {
|
||||
req, err = http.NewRequest(method, httpURL, nil)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
res, err := cli.Client.Do(req)
|
||||
if res != nil {
|
||||
defer res.Body.Close()
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
contents, err := ioutil.ReadAll(res.Body)
|
||||
if res.StatusCode/100 != 2 { // not 2xx
|
||||
var wrap error
|
||||
var respErr RespError
|
||||
if _ = json.Unmarshal(contents, &respErr); respErr.ErrCode != "" {
|
||||
wrap = respErr
|
||||
}
|
||||
|
||||
// If we failed to decode as RespError, don't just drop the HTTP body, include it in the
|
||||
// HTTP error instead (e.g proxy errors which return HTML).
|
||||
msg := "Failed to " + method + " JSON to " + req.URL.Path
|
||||
if wrap == nil {
|
||||
msg = msg + ": " + string(contents)
|
||||
}
|
||||
|
||||
return contents, HTTPError{
|
||||
Code: res.StatusCode,
|
||||
Message: msg,
|
||||
WrappedError: wrap,
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resBody != nil {
|
||||
if err = json.Unmarshal(contents, &resBody); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return contents, nil
|
||||
}
|
||||
|
||||
// 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) {
|
||||
urlPath := cli.BuildURL("user", cli.UserID, "filter")
|
||||
_, err = cli.MakeRequest("POST", urlPath, &filter, &resp)
|
||||
return
|
||||
}
|
||||
|
||||
// SyncRequest makes an HTTP request according to http://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-sync
|
||||
func (cli *Client) SyncRequest(timeout int, since, filterID string, fullState bool, setPresence string) (resp *RespSync, err error) {
|
||||
query := map[string]string{
|
||||
"timeout": strconv.Itoa(timeout),
|
||||
}
|
||||
if since != "" {
|
||||
query["since"] = since
|
||||
}
|
||||
if filterID != "" {
|
||||
query["filter"] = filterID
|
||||
}
|
||||
if setPresence != "" {
|
||||
query["set_presence"] = setPresence
|
||||
}
|
||||
if fullState {
|
||||
query["full_state"] = "true"
|
||||
}
|
||||
urlPath := cli.BuildURLWithQuery([]string{"sync"}, query)
|
||||
_, err = cli.MakeRequest("GET", urlPath, nil, &resp)
|
||||
return
|
||||
}
|
||||
|
||||
func (cli *Client) register(u string, req *ReqRegister) (resp *RespRegister, uiaResp *RespUserInteractive, err error) {
|
||||
var bodyBytes []byte
|
||||
bodyBytes, err = cli.MakeRequest("POST", u, req, nil)
|
||||
if err != nil {
|
||||
httpErr, ok := err.(HTTPError)
|
||||
if !ok { // network error
|
||||
return
|
||||
}
|
||||
if httpErr.Code == 401 {
|
||||
// body should be RespUserInteractive, if it isn't, fail with the error
|
||||
err = json.Unmarshal(bodyBytes, &uiaResp)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
// body should be RespRegister
|
||||
err = json.Unmarshal(bodyBytes, &resp)
|
||||
return
|
||||
}
|
||||
|
||||
// Register makes an HTTP request according to http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-register
|
||||
//
|
||||
// Registers with kind=user. For kind=guest, see RegisterGuest.
|
||||
func (cli *Client) Register(req *ReqRegister) (*RespRegister, *RespUserInteractive, error) {
|
||||
u := cli.BuildURL("register")
|
||||
return cli.register(u, req)
|
||||
}
|
||||
|
||||
// RegisterGuest makes an HTTP request according to http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-register
|
||||
// with kind=guest.
|
||||
//
|
||||
// For kind=user, see Register.
|
||||
func (cli *Client) RegisterGuest(req *ReqRegister) (*RespRegister, *RespUserInteractive, error) {
|
||||
query := map[string]string{
|
||||
"kind": "guest",
|
||||
}
|
||||
u := cli.BuildURLWithQuery([]string{"register"}, query)
|
||||
return cli.register(u, req)
|
||||
}
|
||||
|
||||
// RegisterDummy performs m.login.dummy registration according to https://matrix.org/docs/spec/client_server/r0.2.0.html#dummy-auth
|
||||
//
|
||||
// Only a username and password need to be provided on the ReqRegister struct. Most local/developer homeservers will allow registration
|
||||
// this way. If the homeserver does not, an error is returned.
|
||||
//
|
||||
// This does not set credentials on the client instance. See SetCredentials() instead.
|
||||
//
|
||||
// res, err := cli.RegisterDummy(&gomatrix.ReqRegister{
|
||||
// Username: "alice",
|
||||
// Password: "wonderland",
|
||||
// })
|
||||
// if err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
// token := res.AccessToken
|
||||
func (cli *Client) RegisterDummy(req *ReqRegister) (*RespRegister, error) {
|
||||
res, uia, err := cli.Register(req)
|
||||
if err != nil && uia == nil {
|
||||
return nil, err
|
||||
}
|
||||
if uia != nil && uia.HasSingleStageFlow("m.login.dummy") {
|
||||
req.Auth = struct {
|
||||
Type string `json:"type"`
|
||||
Session string `json:"session,omitempty"`
|
||||
}{"m.login.dummy", uia.Session}
|
||||
res, _, err = cli.Register(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if res == nil {
|
||||
return nil, fmt.Errorf("registration failed: does this server support m.login.dummy?")
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// Login a user to the homeserver according to http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-login
|
||||
// This does not set credentials on this client instance. See SetCredentials() instead.
|
||||
func (cli *Client) Login(req *ReqLogin) (resp *RespLogin, err error) {
|
||||
urlPath := cli.BuildURL("login")
|
||||
_, err = cli.MakeRequest("POST", urlPath, req, &resp)
|
||||
return
|
||||
}
|
||||
|
||||
// Logout the current user. See http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-logout
|
||||
// This does not clear the credentials from the client instance. See ClearCredentials() instead.
|
||||
func (cli *Client) Logout() (resp *RespLogout, err error) {
|
||||
urlPath := cli.BuildURL("logout")
|
||||
_, err = cli.MakeRequest("POST", urlPath, nil, &resp)
|
||||
return
|
||||
}
|
||||
|
||||
// Versions returns the list of supported Matrix versions on this homeserver. See http://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-versions
|
||||
func (cli *Client) Versions() (resp *RespVersions, err error) {
|
||||
urlPath := cli.BuildBaseURL("_matrix", "client", "versions")
|
||||
_, err = cli.MakeRequest("GET", urlPath, nil, &resp)
|
||||
return
|
||||
}
|
||||
|
||||
// JoinRoom joins the client to a room ID or alias. See http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-join-roomidoralias
|
||||
//
|
||||
// If serverName is specified, this will be added as a query param to instruct the homeserver to join via that server. If content is specified, it will
|
||||
// be JSON encoded and used as the request body.
|
||||
func (cli *Client) JoinRoom(roomIDorAlias, serverName string, content interface{}) (resp *RespJoinRoom, err error) {
|
||||
var urlPath string
|
||||
if serverName != "" {
|
||||
urlPath = cli.BuildURLWithQuery([]string{"join", roomIDorAlias}, map[string]string{
|
||||
"server_name": serverName,
|
||||
})
|
||||
} else {
|
||||
urlPath = cli.BuildURL("join", roomIDorAlias)
|
||||
}
|
||||
_, err = cli.MakeRequest("POST", urlPath, content, &resp)
|
||||
return
|
||||
}
|
||||
|
||||
// SetDisplayName sets the user's profile display name. See http://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-profile-userid-displayname
|
||||
func (cli *Client) SetDisplayName(displayName string) (err error) {
|
||||
urlPath := cli.BuildURL("profile", cli.UserID, "displayname")
|
||||
s := struct {
|
||||
DisplayName string `json:"displayname"`
|
||||
}{displayName}
|
||||
_, err = cli.MakeRequest("PUT", urlPath, &s, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// GetAvatarURL gets the user's avatar URL. See http://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-profile-userid-avatar-url
|
||||
func (cli *Client) GetAvatarURL() (url string, err error) {
|
||||
urlPath := cli.BuildURL("profile", cli.UserID, "avatar_url")
|
||||
s := struct {
|
||||
AvatarURL string `json:"avatar_url"`
|
||||
}{}
|
||||
|
||||
_, err = cli.MakeRequest("GET", urlPath, nil, &s)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return s.AvatarURL, nil
|
||||
}
|
||||
|
||||
// SetAvatarURL sets the user's avatar URL. See http://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-profile-userid-avatar-url
|
||||
func (cli *Client) SetAvatarURL(url string) (err error) {
|
||||
urlPath := cli.BuildURL("profile", cli.UserID, "avatar_url")
|
||||
s := struct {
|
||||
AvatarURL string `json:"avatar_url"`
|
||||
}{url}
|
||||
_, err = cli.MakeRequest("PUT", urlPath, &s, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SendMessageEvent sends a message event into a room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-send-eventtype-txnid
|
||||
// contentJSON should be a pointer to something that can be encoded as JSON using json.Marshal.
|
||||
func (cli *Client) SendMessageEvent(roomID string, eventType string, contentJSON interface{}) (resp *RespSendEvent, err error) {
|
||||
txnID := txnID()
|
||||
urlPath := cli.BuildURL("rooms", roomID, "send", eventType, txnID)
|
||||
_, err = cli.MakeRequest("PUT", urlPath, contentJSON, &resp)
|
||||
return
|
||||
}
|
||||
|
||||
// SendStateEvent sends a state event into a room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-state-eventtype-statekey
|
||||
// contentJSON should be a pointer to something that can be encoded as JSON using json.Marshal.
|
||||
func (cli *Client) SendStateEvent(roomID, eventType, stateKey string, contentJSON interface{}) (resp *RespSendEvent, err error) {
|
||||
urlPath := cli.BuildURL("rooms", roomID, "state", eventType, stateKey)
|
||||
_, err = cli.MakeRequest("PUT", urlPath, contentJSON, &resp)
|
||||
return
|
||||
}
|
||||
|
||||
// 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, text string) (*RespSendEvent, error) {
|
||||
return cli.SendMessageEvent(roomID, "m.room.message",
|
||||
TextMessage{"m.text", text})
|
||||
}
|
||||
|
||||
// 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, body, url string) (*RespSendEvent, error) {
|
||||
return cli.SendMessageEvent(roomID, "m.room.message",
|
||||
ImageMessage{
|
||||
MsgType: "m.image",
|
||||
Body: body,
|
||||
URL: url,
|
||||
})
|
||||
}
|
||||
|
||||
// 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, body, url string) (*RespSendEvent, error) {
|
||||
return cli.SendMessageEvent(roomID, "m.room.message",
|
||||
VideoMessage{
|
||||
MsgType: "m.video",
|
||||
Body: body,
|
||||
URL: url,
|
||||
})
|
||||
}
|
||||
|
||||
// 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, text string) (*RespSendEvent, error) {
|
||||
return cli.SendMessageEvent(roomID, "m.room.message",
|
||||
TextMessage{"m.notice", text})
|
||||
}
|
||||
|
||||
// RedactEvent redacts the given event. See http://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-redact-eventid-txnid
|
||||
func (cli *Client) RedactEvent(roomID, eventID string, req *ReqRedact) (resp *RespSendEvent, err error) {
|
||||
txnID := txnID()
|
||||
urlPath := cli.BuildURL("rooms", roomID, "redact", eventID, txnID)
|
||||
_, err = cli.MakeRequest("PUT", urlPath, req, &resp)
|
||||
return
|
||||
}
|
||||
|
||||
// CreateRoom creates a new Matrix room. See https://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-createroom
|
||||
// resp, err := cli.CreateRoom(&gomatrix.ReqCreateRoom{
|
||||
// Preset: "public_chat",
|
||||
// })
|
||||
// fmt.Println("Room:", resp.RoomID)
|
||||
func (cli *Client) CreateRoom(req *ReqCreateRoom) (resp *RespCreateRoom, err error) {
|
||||
urlPath := cli.BuildURL("createRoom")
|
||||
_, err = cli.MakeRequest("POST", urlPath, req, &resp)
|
||||
return
|
||||
}
|
||||
|
||||
// LeaveRoom leaves the given room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-leave
|
||||
func (cli *Client) LeaveRoom(roomID string) (resp *RespLeaveRoom, err error) {
|
||||
u := cli.BuildURL("rooms", roomID, "leave")
|
||||
_, err = cli.MakeRequest("POST", u, struct{}{}, &resp)
|
||||
return
|
||||
}
|
||||
|
||||
// ForgetRoom forgets a room entirely. See http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-forget
|
||||
func (cli *Client) ForgetRoom(roomID string) (resp *RespForgetRoom, err error) {
|
||||
u := cli.BuildURL("rooms", roomID, "forget")
|
||||
_, err = cli.MakeRequest("POST", u, struct{}{}, &resp)
|
||||
return
|
||||
}
|
||||
|
||||
// InviteUser invites a user to a room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-invite
|
||||
func (cli *Client) InviteUser(roomID string, req *ReqInviteUser) (resp *RespInviteUser, err error) {
|
||||
u := cli.BuildURL("rooms", roomID, "invite")
|
||||
_, err = cli.MakeRequest("POST", u, struct{}{}, &resp)
|
||||
return
|
||||
}
|
||||
|
||||
// InviteUserByThirdParty invites a third-party identifier to a room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#invite-by-third-party-id-endpoint
|
||||
func (cli *Client) InviteUserByThirdParty(roomID string, req *ReqInvite3PID) (resp *RespInviteUser, err error) {
|
||||
u := cli.BuildURL("rooms", roomID, "invite")
|
||||
_, err = cli.MakeRequest("POST", u, req, &resp)
|
||||
return
|
||||
}
|
||||
|
||||
// KickUser kicks a user from a room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-kick
|
||||
func (cli *Client) KickUser(roomID string, req *ReqKickUser) (resp *RespKickUser, err error) {
|
||||
u := cli.BuildURL("rooms", roomID, "kick")
|
||||
_, err = cli.MakeRequest("POST", u, req, &resp)
|
||||
return
|
||||
}
|
||||
|
||||
// BanUser bans a user from a room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-ban
|
||||
func (cli *Client) BanUser(roomID string, req *ReqBanUser) (resp *RespBanUser, err error) {
|
||||
u := cli.BuildURL("rooms", roomID, "ban")
|
||||
_, err = cli.MakeRequest("POST", u, req, &resp)
|
||||
return
|
||||
}
|
||||
|
||||
// UnbanUser unbans a user from a room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-unban
|
||||
func (cli *Client) UnbanUser(roomID string, req *ReqUnbanUser) (resp *RespUnbanUser, err error) {
|
||||
u := cli.BuildURL("rooms", roomID, "unban")
|
||||
_, err = cli.MakeRequest("POST", u, req, &resp)
|
||||
return
|
||||
}
|
||||
|
||||
// UserTyping sets the typing status of the user. See https://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-typing-userid
|
||||
func (cli *Client) UserTyping(roomID string, typing bool, timeout int64) (resp *RespTyping, err error) {
|
||||
req := ReqTyping{Typing: typing, Timeout: timeout}
|
||||
u := cli.BuildURL("rooms", roomID, "typing", cli.UserID)
|
||||
_, err = cli.MakeRequest("PUT", u, req, &resp)
|
||||
return
|
||||
}
|
||||
|
||||
// StateEvent gets a single state event in a room. It will attempt to JSON unmarshal into the given "outContent" struct with
|
||||
// the HTTP response body, or return an error.
|
||||
// See http://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-rooms-roomid-state-eventtype-statekey
|
||||
func (cli *Client) StateEvent(roomID, eventType, stateKey string, outContent interface{}) (err error) {
|
||||
u := cli.BuildURL("rooms", roomID, "state", eventType, stateKey)
|
||||
_, err = cli.MakeRequest("GET", u, nil, outContent)
|
||||
return
|
||||
}
|
||||
|
||||
// UploadLink uploads an HTTP URL and then returns an MXC URI.
|
||||
func (cli *Client) UploadLink(link string) (*RespMediaUpload, error) {
|
||||
res, err := cli.Client.Get(link)
|
||||
if res != nil {
|
||||
defer res.Body.Close()
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cli.UploadToContentRepo(res.Body, res.Header.Get("Content-Type"), res.ContentLength)
|
||||
}
|
||||
|
||||
// UploadToContentRepo uploads the given bytes to the content repository and returns an MXC URI.
|
||||
// See http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-media-r0-upload
|
||||
func (cli *Client) UploadToContentRepo(content io.Reader, contentType string, contentLength int64) (*RespMediaUpload, error) {
|
||||
req, err := http.NewRequest("POST", cli.BuildBaseURL("_matrix/media/r0/upload"), content)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("Content-Type", contentType)
|
||||
req.ContentLength = contentLength
|
||||
res, err := cli.Client.Do(req)
|
||||
if res != nil {
|
||||
defer res.Body.Close()
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if res.StatusCode != 200 {
|
||||
contents, err := ioutil.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return nil, HTTPError{
|
||||
Message: "Upload request failed - Failed to read response body: " + err.Error(),
|
||||
Code: res.StatusCode,
|
||||
}
|
||||
}
|
||||
return nil, HTTPError{
|
||||
Message: "Upload request failed: " + string(contents),
|
||||
Code: res.StatusCode,
|
||||
}
|
||||
}
|
||||
var m RespMediaUpload
|
||||
if err := json.NewDecoder(res.Body).Decode(&m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &m, nil
|
||||
}
|
||||
|
||||
// JoinedMembers returns a map of joined room members. See TODO-SPEC. https://github.com/matrix-org/synapse/pull/1680
|
||||
//
|
||||
// In general, usage of this API is discouraged in favour of /sync, as calling this API can race with incoming membership changes.
|
||||
// This API is primarily designed for application services which may want to efficiently look up joined members in a room.
|
||||
func (cli *Client) JoinedMembers(roomID string) (resp *RespJoinedMembers, err error) {
|
||||
u := cli.BuildURL("rooms", roomID, "joined_members")
|
||||
_, err = cli.MakeRequest("GET", u, nil, &resp)
|
||||
return
|
||||
}
|
||||
|
||||
// JoinedRooms returns a list of rooms which the client is joined to. See TODO-SPEC. https://github.com/matrix-org/synapse/pull/1680
|
||||
//
|
||||
// In general, usage of this API is discouraged in favour of /sync, as calling this API can race with incoming membership changes.
|
||||
// This API is primarily designed for application services which may want to efficiently look up joined rooms.
|
||||
func (cli *Client) JoinedRooms() (resp *RespJoinedRooms, err error) {
|
||||
u := cli.BuildURL("joined_rooms")
|
||||
_, err = cli.MakeRequest("GET", u, nil, &resp)
|
||||
return
|
||||
}
|
||||
|
||||
// Messages returns a list of message and state events for a room. It uses
|
||||
// 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, from, to string, dir rune, limit int) (resp *RespMessages, err error) {
|
||||
query := map[string]string{
|
||||
"from": from,
|
||||
"dir": string(dir),
|
||||
}
|
||||
if to != "" {
|
||||
query["to"] = to
|
||||
}
|
||||
if limit != 0 {
|
||||
query["limit"] = string(limit)
|
||||
}
|
||||
|
||||
urlPath := cli.BuildURLWithQuery([]string{"rooms", roomID, "messages"}, query)
|
||||
_, err = cli.MakeRequest("GET", urlPath, nil, &resp)
|
||||
return
|
||||
}
|
||||
|
||||
func txnID() string {
|
||||
return "go" + strconv.FormatInt(time.Now().UnixNano(), 10)
|
||||
}
|
||||
|
||||
// NewClient creates a new Matrix Client ready for syncing
|
||||
func NewClient(homeserverURL, userID, accessToken string) (*Client, error) {
|
||||
hsURL, err := url.Parse(homeserverURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// By default, use an in-memory store which will never save filter ids / next batch tokens to disk.
|
||||
// The client will work with this storer: it just won't remember across restarts.
|
||||
// In practice, a database backend should be used.
|
||||
store := NewInMemoryStore()
|
||||
cli := Client{
|
||||
AccessToken: accessToken,
|
||||
HomeserverURL: hsURL,
|
||||
UserID: userID,
|
||||
Prefix: "/_matrix/client/r0",
|
||||
Syncer: NewDefaultSyncer(userID, store),
|
||||
Store: store,
|
||||
}
|
||||
// By default, use the default HTTP client.
|
||||
cli.Client = http.DefaultClient
|
||||
|
||||
return &cli, nil
|
||||
}
|
119
vendor/src/github.com/matrix-org/gomatrix/client_examples_test.go
vendored
Normal file
119
vendor/src/github.com/matrix-org/gomatrix/client_examples_test.go
vendored
Normal file
|
@ -0,0 +1,119 @@
|
|||
package gomatrix
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func Example_sync() {
|
||||
cli, _ := NewClient("https://matrix.org", "@example:matrix.org", "MDAefhiuwehfuiwe")
|
||||
cli.Store.SaveFilterID("@example:matrix.org", "2") // Optional: if you know it already
|
||||
cli.Store.SaveNextBatch("@example:matrix.org", "111_222_333_444") // Optional: if you know it already
|
||||
syncer := cli.Syncer.(*DefaultSyncer)
|
||||
syncer.OnEventType("m.room.message", func(ev *Event) {
|
||||
fmt.Println("Message: ", ev)
|
||||
})
|
||||
|
||||
// Blocking version
|
||||
if err := cli.Sync(); err != nil {
|
||||
fmt.Println("Sync() returned ", err)
|
||||
}
|
||||
|
||||
// Non-blocking version
|
||||
go func() {
|
||||
for {
|
||||
if err := cli.Sync(); err != nil {
|
||||
fmt.Println("Sync() returned ", err)
|
||||
}
|
||||
// Optional: Wait a period of time before trying to sync again.
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func Example_customInterfaces() {
|
||||
// Custom interfaces must be set prior to calling functions on the client.
|
||||
cli, _ := NewClient("https://matrix.org", "@example:matrix.org", "MDAefhiuwehfuiwe")
|
||||
|
||||
// anything which implements the Storer interface
|
||||
customStore := NewInMemoryStore()
|
||||
cli.Store = customStore
|
||||
|
||||
// anything which implements the Syncer interface
|
||||
customSyncer := NewDefaultSyncer("@example:matrix.org", customStore)
|
||||
cli.Syncer = customSyncer
|
||||
|
||||
// any http.Client
|
||||
cli.Client = http.DefaultClient
|
||||
|
||||
// Once you call a function, you can't safely change the interfaces.
|
||||
cli.SendText("!foo:bar", "Down the rabbit hole")
|
||||
}
|
||||
|
||||
func ExampleClient_BuildURLWithQuery() {
|
||||
cli, _ := NewClient("https://matrix.org", "@example:matrix.org", "abcdef123456")
|
||||
out := cli.BuildURLWithQuery([]string{"sync"}, map[string]string{
|
||||
"filter_id": "5",
|
||||
})
|
||||
fmt.Println(out)
|
||||
// Output: https://matrix.org/_matrix/client/r0/sync?access_token=abcdef123456&filter_id=5
|
||||
}
|
||||
|
||||
func ExampleClient_BuildURL() {
|
||||
userID := "@example:matrix.org"
|
||||
cli, _ := NewClient("https://matrix.org", userID, "abcdef123456")
|
||||
out := cli.BuildURL("user", userID, "filter")
|
||||
fmt.Println(out)
|
||||
// Output: https://matrix.org/_matrix/client/r0/user/@example:matrix.org/filter?access_token=abcdef123456
|
||||
}
|
||||
|
||||
func ExampleClient_BuildBaseURL() {
|
||||
userID := "@example:matrix.org"
|
||||
cli, _ := NewClient("https://matrix.org", userID, "abcdef123456")
|
||||
out := cli.BuildBaseURL("_matrix", "client", "r0", "directory", "room", "#matrix:matrix.org")
|
||||
fmt.Println(out)
|
||||
// Output: https://matrix.org/_matrix/client/r0/directory/room/%23matrix:matrix.org?access_token=abcdef123456
|
||||
}
|
||||
|
||||
// Retrieve the content of a m.room.name state event.
|
||||
func ExampleClient_StateEvent() {
|
||||
content := struct {
|
||||
Name string `json:"name"`
|
||||
}{}
|
||||
cli, _ := NewClient("https://matrix.org", "@example:matrix.org", "abcdef123456")
|
||||
if err := cli.StateEvent("!foo:bar", "m.room.name", "", &content); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Join a room by ID.
|
||||
func ExampleClient_JoinRoom_id() {
|
||||
cli, _ := NewClient("http://localhost:8008", "@example:localhost", "abcdef123456")
|
||||
if _, err := cli.JoinRoom("!uOILRrqxnsYgQdUzar:localhost", "", nil); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Join a room by alias.
|
||||
func ExampleClient_JoinRoom_alias() {
|
||||
cli, _ := NewClient("http://localhost:8008", "@example:localhost", "abcdef123456")
|
||||
if resp, err := cli.JoinRoom("#test:localhost", "", nil); err != nil {
|
||||
panic(err)
|
||||
} else {
|
||||
// Use room ID for something.
|
||||
_ = resp.RoomID
|
||||
}
|
||||
}
|
||||
|
||||
// Login to a local homeserver and set the user ID and access token on success.
|
||||
func ExampleClient_Login() {
|
||||
cli, _ := NewClient("http://localhost:8008", "", "")
|
||||
resp, err := cli.Login(&ReqLogin{
|
||||
Type: "m.login.password",
|
||||
User: "alice",
|
||||
Password: "wonderland",
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
cli.SetCredentials(resp.UserID, resp.AccessToken)
|
||||
}
|
105
vendor/src/github.com/matrix-org/gomatrix/client_test.go
vendored
Normal file
105
vendor/src/github.com/matrix-org/gomatrix/client_test.go
vendored
Normal file
|
@ -0,0 +1,105 @@
|
|||
package gomatrix
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestClient_LeaveRoom(t *testing.T) {
|
||||
cli := mockClient(func(req *http.Request) (*http.Response, error) {
|
||||
if req.Method == "POST" && req.URL.Path == "/_matrix/client/r0/rooms/!foo:bar/leave" {
|
||||
return &http.Response{
|
||||
StatusCode: 200,
|
||||
Body: ioutil.NopCloser(bytes.NewBufferString(`{}`)),
|
||||
}, nil
|
||||
}
|
||||
return nil, fmt.Errorf("unhandled URL: %s", req.URL.Path)
|
||||
})
|
||||
|
||||
if _, err := cli.LeaveRoom("!foo:bar"); err != nil {
|
||||
t.Fatalf("LeaveRoom: error, got %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestClient_GetAvatarUrl(t *testing.T) {
|
||||
cli := mockClient(func(req *http.Request) (*http.Response, error) {
|
||||
if req.Method == "GET" && req.URL.Path == "/_matrix/client/r0/profile/@user:test.gomatrix.org/avatar_url" {
|
||||
return &http.Response{
|
||||
StatusCode: 200,
|
||||
Body: ioutil.NopCloser(bytes.NewBufferString(`{"avatar_url":"mxc://matrix.org/iJaUjkshgdfsdkjfn"}`)),
|
||||
}, nil
|
||||
}
|
||||
return nil, fmt.Errorf("unhandled URL: %s", req.URL.Path)
|
||||
})
|
||||
|
||||
if response, err := cli.GetAvatarURL(); err != nil {
|
||||
t.Fatalf("GetAvatarURL: Got error: %s", err.Error())
|
||||
} else if response == "" {
|
||||
t.Fatal("GetAvatarURL: Got empty response")
|
||||
} else if response != "mxc://matrix.org/iJaUjkshgdfsdkjfn" {
|
||||
t.Fatalf("Unexpected response URL: %s", response)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestClient_SetAvatarUrl(t *testing.T) {
|
||||
cli := mockClient(func(req *http.Request) (*http.Response, error) {
|
||||
if req.Method == "PUT" && req.URL.Path == "/_matrix/client/r0/profile/@user:test.gomatrix.org/avatar_url" {
|
||||
return &http.Response{
|
||||
StatusCode: 200,
|
||||
Body: ioutil.NopCloser(bytes.NewBufferString(`{}`)),
|
||||
}, nil
|
||||
}
|
||||
return nil, fmt.Errorf("unhandled URL: %s", req.URL.Path)
|
||||
})
|
||||
|
||||
if err := cli.SetAvatarURL("https://foo.com/bar.png"); err != nil {
|
||||
t.Fatalf("GetAvatarURL: Got error: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestClient_StateEvent(t *testing.T) {
|
||||
cli := mockClient(func(req *http.Request) (*http.Response, error) {
|
||||
if req.Method == "GET" && req.URL.Path == "/_matrix/client/r0/rooms/!foo:bar/state/m.room.name" {
|
||||
return &http.Response{
|
||||
StatusCode: 200,
|
||||
Body: ioutil.NopCloser(bytes.NewBufferString(`{"name":"Room Name Goes Here"}`)),
|
||||
}, nil
|
||||
}
|
||||
return nil, fmt.Errorf("unhandled URL: %s", req.URL.Path)
|
||||
})
|
||||
|
||||
content := struct {
|
||||
Name string `json:"name"`
|
||||
}{}
|
||||
|
||||
if err := cli.StateEvent("!foo:bar", "m.room.name", "", &content); err != nil {
|
||||
t.Fatalf("StateEvent: error, got %s", err.Error())
|
||||
}
|
||||
if content.Name != "Room Name Goes Here" {
|
||||
t.Fatalf("StateEvent: got %s, want %s", content.Name, "Room Name Goes Here")
|
||||
}
|
||||
}
|
||||
|
||||
func mockClient(fn func(*http.Request) (*http.Response, error)) *Client {
|
||||
mrt := MockRoundTripper{
|
||||
RT: fn,
|
||||
}
|
||||
|
||||
cli, _ := NewClient("https://test.gomatrix.org", "@user:test.gomatrix.org", "abcdef")
|
||||
cli.Client = &http.Client{
|
||||
Transport: mrt,
|
||||
}
|
||||
return cli
|
||||
}
|
||||
|
||||
type MockRoundTripper struct {
|
||||
RT func(*http.Request) (*http.Response, error)
|
||||
}
|
||||
|
||||
func (t MockRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
return t.RT(req)
|
||||
}
|
101
vendor/src/github.com/matrix-org/gomatrix/events.go
vendored
Normal file
101
vendor/src/github.com/matrix-org/gomatrix/events.go
vendored
Normal file
|
@ -0,0 +1,101 @@
|
|||
package gomatrix
|
||||
|
||||
import (
|
||||
"html"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
// 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.
|
||||
Sender string `json:"sender"` // The user ID of the sender of the event
|
||||
Type string `json:"type"` // The event type
|
||||
Timestamp int `json:"origin_server_ts"` // The unix timestamp when this message was sent by the origin server
|
||||
ID string `json:"event_id"` // The unique ID of this event
|
||||
RoomID string `json:"room_id"` // The room the event was sent to. May be nil (e.g. for presence)
|
||||
Content map[string]interface{} `json:"content"` // The JSON content of the event.
|
||||
}
|
||||
|
||||
// Body returns the value of the "body" key in the event content if it is
|
||||
// present and is a string.
|
||||
func (event *Event) Body() (body string, ok bool) {
|
||||
value, exists := event.Content["body"]
|
||||
if !exists {
|
||||
return
|
||||
}
|
||||
body, ok = value.(string)
|
||||
return
|
||||
}
|
||||
|
||||
// MessageType returns the value of the "msgtype" key in the event content if
|
||||
// it is present and is a string.
|
||||
func (event *Event) MessageType() (msgtype string, ok bool) {
|
||||
value, exists := event.Content["msgtype"]
|
||||
if !exists {
|
||||
return
|
||||
}
|
||||
msgtype, ok = value.(string)
|
||||
return
|
||||
}
|
||||
|
||||
// TextMessage is the contents of a Matrix formated message event.
|
||||
type TextMessage struct {
|
||||
MsgType string `json:"msgtype"`
|
||||
Body string `json:"body"`
|
||||
}
|
||||
|
||||
// ImageInfo contains info about an image - http://matrix.org/docs/spec/client_server/r0.2.0.html#m-image
|
||||
type ImageInfo struct {
|
||||
Height uint `json:"h,omitempty"`
|
||||
Width uint `json:"w,omitempty"`
|
||||
Mimetype string `json:"mimetype,omitempty"`
|
||||
Size uint `json:"size,omitempty"`
|
||||
}
|
||||
|
||||
// VideoInfo contains info about a video - http://matrix.org/docs/spec/client_server/r0.2.0.html#m-video
|
||||
type VideoInfo struct {
|
||||
Mimetype string `json:"mimetype,omitempty"`
|
||||
ThumbnailInfo ImageInfo `json:"thumbnail_info"`
|
||||
ThumbnailURL string `json:"thumbnail_url,omitempty"`
|
||||
Height uint `json:"h,omitempty"`
|
||||
Width uint `json:"w,omitempty"`
|
||||
Duration uint `json:"duration,omitempty"`
|
||||
Size uint `json:"size,omitempty"`
|
||||
}
|
||||
|
||||
// VideoMessage is an m.video - http://matrix.org/docs/spec/client_server/r0.2.0.html#m-video
|
||||
type VideoMessage struct {
|
||||
MsgType string `json:"msgtype"`
|
||||
Body string `json:"body"`
|
||||
URL string `json:"url"`
|
||||
Info VideoInfo `json:"info"`
|
||||
}
|
||||
|
||||
// ImageMessage is an m.image event
|
||||
type ImageMessage struct {
|
||||
MsgType string `json:"msgtype"`
|
||||
Body string `json:"body"`
|
||||
URL string `json:"url"`
|
||||
Info ImageInfo `json:"info"`
|
||||
}
|
||||
|
||||
// An HTMLMessage is the contents of a Matrix HTML formated message event.
|
||||
type HTMLMessage struct {
|
||||
Body string `json:"body"`
|
||||
MsgType string `json:"msgtype"`
|
||||
Format string `json:"format"`
|
||||
FormattedBody string `json:"formatted_body"`
|
||||
}
|
||||
|
||||
var htmlRegex = regexp.MustCompile("<[^<]+?>")
|
||||
|
||||
// GetHTMLMessage returns an HTMLMessage with the body set to a stripped version of the provided HTML, in addition
|
||||
// to the provided HTML.
|
||||
func GetHTMLMessage(msgtype, htmlText string) HTMLMessage {
|
||||
return HTMLMessage{
|
||||
Body: html.UnescapeString(htmlRegex.ReplaceAllLiteralString(htmlText, "")),
|
||||
MsgType: msgtype,
|
||||
Format: "org.matrix.custom.html",
|
||||
FormattedBody: htmlText,
|
||||
}
|
||||
}
|
5
vendor/src/github.com/matrix-org/gomatrix/hooks/install.sh
vendored
Normal file
5
vendor/src/github.com/matrix-org/gomatrix/hooks/install.sh
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
#! /bin/bash
|
||||
|
||||
DOT_GIT="$(dirname $0)/../.git"
|
||||
|
||||
ln -s "../../hooks/pre-commit" "$DOT_GIT/hooks/pre-commit"
|
9
vendor/src/github.com/matrix-org/gomatrix/hooks/pre-commit
vendored
Normal file
9
vendor/src/github.com/matrix-org/gomatrix/hooks/pre-commit
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
#! /bin/bash
|
||||
|
||||
set -eu
|
||||
|
||||
golint
|
||||
go fmt
|
||||
go tool vet --all --shadow .
|
||||
gocyclo -over 12 .
|
||||
go test -timeout 5s -test.v
|
78
vendor/src/github.com/matrix-org/gomatrix/requests.go
vendored
Normal file
78
vendor/src/github.com/matrix-org/gomatrix/requests.go
vendored
Normal file
|
@ -0,0 +1,78 @@
|
|||
package gomatrix
|
||||
|
||||
// ReqRegister is the JSON request for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-register
|
||||
type ReqRegister struct {
|
||||
Username string `json:"username,omitempty"`
|
||||
BindEmail bool `json:"bind_email,omitempty"`
|
||||
Password string `json:"password,omitempty"`
|
||||
DeviceID string `json:"device_id,omitempty"`
|
||||
InitialDeviceDisplayName string `json:"initial_device_display_name"`
|
||||
Auth interface{} `json:"auth,omitempty"`
|
||||
}
|
||||
|
||||
// ReqLogin is the JSON request for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-login
|
||||
type ReqLogin struct {
|
||||
Type string `json:"type"`
|
||||
Password string `json:"password,omitempty"`
|
||||
Medium string `json:"medium,omitempty"`
|
||||
User string `json:"user,omitempty"`
|
||||
Address string `json:"address,omitempty"`
|
||||
Token string `json:"token,omitempty"`
|
||||
DeviceID string `json:"device_id,omitempty"`
|
||||
InitialDeviceDisplayName string `json:"initial_device_display_name,omitempty"`
|
||||
}
|
||||
|
||||
// ReqCreateRoom is the JSON request for https://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-createroom
|
||||
type ReqCreateRoom struct {
|
||||
Visibility string `json:"visibility,omitempty"`
|
||||
RoomAliasName string `json:"room_alias_name,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Topic string `json:"topic,omitempty"`
|
||||
Invite []string `json:"invite,omitempty"`
|
||||
Invite3PID []ReqInvite3PID `json:"invite_3pid,omitempty"`
|
||||
CreationContent map[string]interface{} `json:"creation_content,omitempty"`
|
||||
InitialState []Event `json:"initial_state,omitempty"`
|
||||
Preset string `json:"preset,omitempty"`
|
||||
IsDirect bool `json:"is_direct,omitempty"`
|
||||
}
|
||||
|
||||
// ReqRedact is the JSON request for http://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-redact-eventid-txnid
|
||||
type ReqRedact struct {
|
||||
Reason string `json:"reason,omitempty"`
|
||||
}
|
||||
|
||||
// ReqInvite3PID is the JSON request for https://matrix.org/docs/spec/client_server/r0.2.0.html#id57
|
||||
// It is also a JSON object used in https://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-createroom
|
||||
type ReqInvite3PID struct {
|
||||
IDServer string `json:"id_server"`
|
||||
Medium string `json:"medium"`
|
||||
Address string `json:"address"`
|
||||
}
|
||||
|
||||
// ReqInviteUser is the JSON request for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-invite
|
||||
type ReqInviteUser struct {
|
||||
UserID string `json:"user_id"`
|
||||
}
|
||||
|
||||
// ReqKickUser is the JSON request for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-kick
|
||||
type ReqKickUser struct {
|
||||
Reason string `json:"reason,omitempty"`
|
||||
UserID string `json:"user_id"`
|
||||
}
|
||||
|
||||
// ReqBanUser is the JSON request for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-ban
|
||||
type ReqBanUser struct {
|
||||
Reason string `json:"reason,omitempty"`
|
||||
UserID string `json:"user_id"`
|
||||
}
|
||||
|
||||
// ReqUnbanUser is the JSON request for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-unban
|
||||
type ReqUnbanUser struct {
|
||||
UserID string `json:"user_id"`
|
||||
}
|
||||
|
||||
// ReqTyping is the JSON request for https://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-typing-userid
|
||||
type ReqTyping struct {
|
||||
Typing bool `json:"typing"`
|
||||
Timeout int64 `json:"timeout"`
|
||||
}
|
164
vendor/src/github.com/matrix-org/gomatrix/responses.go
vendored
Normal file
164
vendor/src/github.com/matrix-org/gomatrix/responses.go
vendored
Normal file
|
@ -0,0 +1,164 @@
|
|||
package gomatrix
|
||||
|
||||
// RespError is the standard JSON error response from Homeservers. It also implements the Golang "error" interface.
|
||||
// See http://matrix.org/docs/spec/client_server/r0.2.0.html#api-standards
|
||||
type RespError struct {
|
||||
ErrCode string `json:"errcode"`
|
||||
Err string `json:"error"`
|
||||
}
|
||||
|
||||
// Error returns the errcode and error message.
|
||||
func (e RespError) Error() string {
|
||||
return e.ErrCode + ": " + e.Err
|
||||
}
|
||||
|
||||
// RespCreateFilter is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-user-userid-filter
|
||||
type RespCreateFilter struct {
|
||||
FilterID string `json:"filter_id"`
|
||||
}
|
||||
|
||||
// RespVersions is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-versions
|
||||
type RespVersions struct {
|
||||
Versions []string `json:"versions"`
|
||||
}
|
||||
|
||||
// RespJoinRoom is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-join
|
||||
type RespJoinRoom struct {
|
||||
RoomID string `json:"room_id"`
|
||||
}
|
||||
|
||||
// RespLeaveRoom is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-leave
|
||||
type RespLeaveRoom struct{}
|
||||
|
||||
// RespForgetRoom is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-forget
|
||||
type RespForgetRoom struct{}
|
||||
|
||||
// RespInviteUser is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-invite
|
||||
type RespInviteUser struct{}
|
||||
|
||||
// RespKickUser is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-kick
|
||||
type RespKickUser struct{}
|
||||
|
||||
// RespBanUser is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-ban
|
||||
type RespBanUser struct{}
|
||||
|
||||
// RespUnbanUser is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-unban
|
||||
type RespUnbanUser struct{}
|
||||
|
||||
// RespTyping is the JSON response for https://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-typing-userid
|
||||
type RespTyping struct{}
|
||||
|
||||
// RespJoinedRooms is the JSON response for TODO-SPEC https://github.com/matrix-org/synapse/pull/1680
|
||||
type RespJoinedRooms struct {
|
||||
JoinedRooms []string `json:"joined_rooms"`
|
||||
}
|
||||
|
||||
// RespJoinedMembers is the JSON response for TODO-SPEC https://github.com/matrix-org/synapse/pull/1680
|
||||
type RespJoinedMembers struct {
|
||||
Joined map[string]struct {
|
||||
DisplayName *string `json:"display_name"`
|
||||
AvatarURL *string `json:"avatar_url"`
|
||||
} `json:"joined"`
|
||||
}
|
||||
|
||||
// RespMessages is the JSON response for https://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-rooms-roomid-messages
|
||||
type RespMessages struct {
|
||||
Start string `json:"start"`
|
||||
Chunk []Event `json:"chunk"`
|
||||
End string `json:"end"`
|
||||
}
|
||||
|
||||
// RespSendEvent is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-send-eventtype-txnid
|
||||
type RespSendEvent struct {
|
||||
EventID string `json:"event_id"`
|
||||
}
|
||||
|
||||
// RespMediaUpload is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-media-r0-upload
|
||||
type RespMediaUpload struct {
|
||||
ContentURI string `json:"content_uri"`
|
||||
}
|
||||
|
||||
// RespUserInteractive is the JSON response for https://matrix.org/docs/spec/client_server/r0.2.0.html#user-interactive-authentication-api
|
||||
type RespUserInteractive struct {
|
||||
Flows []struct {
|
||||
Stages []string `json:"stages"`
|
||||
} `json:"flows"`
|
||||
Params map[string]interface{} `json:"params"`
|
||||
Session string `json:"string"`
|
||||
Completed []string `json:"completed"`
|
||||
ErrCode string `json:"errcode"`
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
// HasSingleStageFlow returns true if there exists at least 1 Flow with a single stage of stageName.
|
||||
func (r RespUserInteractive) HasSingleStageFlow(stageName string) bool {
|
||||
for _, f := range r.Flows {
|
||||
if len(f.Stages) == 1 && f.Stages[0] == stageName {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// RespRegister is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-register
|
||||
type RespRegister struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
DeviceID string `json:"device_id"`
|
||||
HomeServer string `json:"home_server"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
UserID string `json:"user_id"`
|
||||
}
|
||||
|
||||
// RespLogin is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-login
|
||||
type RespLogin struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
DeviceID string `json:"device_id"`
|
||||
HomeServer string `json:"home_server"`
|
||||
UserID string `json:"user_id"`
|
||||
}
|
||||
|
||||
// RespLogout is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-logout
|
||||
type RespLogout struct{}
|
||||
|
||||
// RespCreateRoom is the JSON response for https://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-createroom
|
||||
type RespCreateRoom struct {
|
||||
RoomID string `json:"room_id"`
|
||||
}
|
||||
|
||||
// RespSync is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-sync
|
||||
type RespSync struct {
|
||||
NextBatch string `json:"next_batch"`
|
||||
AccountData struct {
|
||||
Events []Event `json:"events"`
|
||||
} `json:"account_data"`
|
||||
Presence struct {
|
||||
Events []Event `json:"events"`
|
||||
} `json:"presence"`
|
||||
Rooms struct {
|
||||
Leave map[string]struct {
|
||||
State struct {
|
||||
Events []Event `json:"events"`
|
||||
} `json:"state"`
|
||||
Timeline struct {
|
||||
Events []Event `json:"events"`
|
||||
Limited bool `json:"limited"`
|
||||
PrevBatch string `json:"prev_batch"`
|
||||
} `json:"timeline"`
|
||||
} `json:"leave"`
|
||||
Join map[string]struct {
|
||||
State struct {
|
||||
Events []Event `json:"events"`
|
||||
} `json:"state"`
|
||||
Timeline struct {
|
||||
Events []Event `json:"events"`
|
||||
Limited bool `json:"limited"`
|
||||
PrevBatch string `json:"prev_batch"`
|
||||
} `json:"timeline"`
|
||||
} `json:"join"`
|
||||
Invite map[string]struct {
|
||||
State struct {
|
||||
Events []Event
|
||||
} `json:"invite_state"`
|
||||
} `json:"invite"`
|
||||
} `json:"rooms"`
|
||||
}
|
50
vendor/src/github.com/matrix-org/gomatrix/room.go
vendored
Normal file
50
vendor/src/github.com/matrix-org/gomatrix/room.go
vendored
Normal file
|
@ -0,0 +1,50 @@
|
|||
package gomatrix
|
||||
|
||||
// Room represents a single Matrix room.
|
||||
type Room struct {
|
||||
ID string
|
||||
State map[string]map[string]*Event
|
||||
}
|
||||
|
||||
// UpdateState updates the room's current state with the given Event. This will clobber events based
|
||||
// on the type/state_key combination.
|
||||
func (room Room) UpdateState(event *Event) {
|
||||
_, exists := room.State[event.Type]
|
||||
if !exists {
|
||||
room.State[event.Type] = make(map[string]*Event)
|
||||
}
|
||||
room.State[event.Type][*event.StateKey] = event
|
||||
}
|
||||
|
||||
// GetStateEvent returns the state event for the given type/state_key combo, or nil.
|
||||
func (room Room) GetStateEvent(eventType string, stateKey string) *Event {
|
||||
stateEventMap, _ := room.State[eventType]
|
||||
event, _ := stateEventMap[stateKey]
|
||||
return event
|
||||
}
|
||||
|
||||
// GetMembershipState returns the membership state of the given user ID in this room. If there is
|
||||
// no entry for this member, 'leave' is returned for consistency with left users.
|
||||
func (room Room) GetMembershipState(userID string) string {
|
||||
state := "leave"
|
||||
event := room.GetStateEvent("m.room.member", userID)
|
||||
if event != nil {
|
||||
membershipState, found := event.Content["membership"]
|
||||
if found {
|
||||
mState, isString := membershipState.(string)
|
||||
if isString {
|
||||
state = mState
|
||||
}
|
||||
}
|
||||
}
|
||||
return state
|
||||
}
|
||||
|
||||
// NewRoom creates a new Room with the given ID
|
||||
func NewRoom(roomID string) *Room {
|
||||
// Init the State map and return a pointer to the Room
|
||||
return &Room{
|
||||
ID: roomID,
|
||||
State: make(map[string]map[string]*Event),
|
||||
}
|
||||
}
|
65
vendor/src/github.com/matrix-org/gomatrix/store.go
vendored
Normal file
65
vendor/src/github.com/matrix-org/gomatrix/store.go
vendored
Normal file
|
@ -0,0 +1,65 @@
|
|||
package gomatrix
|
||||
|
||||
// Storer is an interface which must be satisfied to store client data.
|
||||
//
|
||||
// You can either write a struct which persists this data to disk, or you can use the
|
||||
// provided "InMemoryStore" which just keeps data around in-memory which is lost on
|
||||
// restarts.
|
||||
type Storer interface {
|
||||
SaveFilterID(userID, filterID string)
|
||||
LoadFilterID(userID string) string
|
||||
SaveNextBatch(userID, nextBatchToken string)
|
||||
LoadNextBatch(userID string) string
|
||||
SaveRoom(room *Room)
|
||||
LoadRoom(roomID string) *Room
|
||||
}
|
||||
|
||||
// InMemoryStore implements the Storer interface.
|
||||
//
|
||||
// Everything is persisted in-memory as maps. It is not safe to load/save filter IDs
|
||||
// or next batch tokens on any goroutine other than the syncing goroutine: the one
|
||||
// which called Client.Sync().
|
||||
type InMemoryStore struct {
|
||||
Filters map[string]string
|
||||
NextBatch map[string]string
|
||||
Rooms map[string]*Room
|
||||
}
|
||||
|
||||
// SaveFilterID to memory.
|
||||
func (s *InMemoryStore) SaveFilterID(userID, filterID string) {
|
||||
s.Filters[userID] = filterID
|
||||
}
|
||||
|
||||
// LoadFilterID from memory.
|
||||
func (s *InMemoryStore) LoadFilterID(userID string) string {
|
||||
return s.Filters[userID]
|
||||
}
|
||||
|
||||
// SaveNextBatch to memory.
|
||||
func (s *InMemoryStore) SaveNextBatch(userID, nextBatchToken string) {
|
||||
s.NextBatch[userID] = nextBatchToken
|
||||
}
|
||||
|
||||
// LoadNextBatch from memory.
|
||||
func (s *InMemoryStore) LoadNextBatch(userID string) string {
|
||||
return s.NextBatch[userID]
|
||||
}
|
||||
|
||||
// SaveRoom to memory.
|
||||
func (s *InMemoryStore) SaveRoom(room *Room) {
|
||||
s.Rooms[room.ID] = room
|
||||
}
|
||||
|
||||
// LoadRoom from memory.
|
||||
func (s *InMemoryStore) LoadRoom(roomID string) *Room {
|
||||
return s.Rooms[roomID]
|
||||
}
|
||||
|
||||
// NewInMemoryStore constructs a new InMemoryStore.
|
||||
func NewInMemoryStore() *InMemoryStore {
|
||||
return &InMemoryStore{
|
||||
Filters: make(map[string]string),
|
||||
NextBatch: make(map[string]string),
|
||||
Rooms: make(map[string]*Room),
|
||||
}
|
||||
}
|
164
vendor/src/github.com/matrix-org/gomatrix/sync.go
vendored
Normal file
164
vendor/src/github.com/matrix-org/gomatrix/sync.go
vendored
Normal file
|
@ -0,0 +1,164 @@
|
|||
package gomatrix
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"runtime/debug"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Syncer represents an interface that must be satisfied in order to do /sync requests on a client.
|
||||
type Syncer interface {
|
||||
// Process the /sync response. The since parameter is the since= value that was used to produce the response.
|
||||
// This is useful for detecting the very first sync (since=""). If an error is return, Syncing will be stopped
|
||||
// permanently.
|
||||
ProcessResponse(resp *RespSync, since string) error
|
||||
// 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 string) json.RawMessage
|
||||
}
|
||||
|
||||
// DefaultSyncer is the default syncing implementation. You can either write your own syncer, or selectively
|
||||
// replace parts of this default syncer (e.g. the ProcessResponse method). The default syncer uses the observer
|
||||
// pattern to notify callers about incoming events. See DefaultSyncer.OnEventType for more information.
|
||||
type DefaultSyncer struct {
|
||||
UserID string
|
||||
Store Storer
|
||||
listeners map[string][]OnEventListener // event type to listeners array
|
||||
}
|
||||
|
||||
// OnEventListener can be used with DefaultSyncer.OnEventType to be informed of incoming events.
|
||||
type OnEventListener func(*Event)
|
||||
|
||||
// NewDefaultSyncer returns an instantiated DefaultSyncer
|
||||
func NewDefaultSyncer(userID string, store Storer) *DefaultSyncer {
|
||||
return &DefaultSyncer{
|
||||
UserID: userID,
|
||||
Store: store,
|
||||
listeners: make(map[string][]OnEventListener),
|
||||
}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
if !s.shouldProcessResponse(res, since) {
|
||||
return
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err = fmt.Errorf("ProcessResponse panicked! userID=%s since=%s panic=%s\n%s", s.UserID, since, r, debug.Stack())
|
||||
}
|
||||
}()
|
||||
|
||||
for roomID, roomData := range res.Rooms.Join {
|
||||
room := s.getOrCreateRoom(roomID)
|
||||
for _, event := range roomData.State.Events {
|
||||
event.RoomID = roomID
|
||||
room.UpdateState(&event)
|
||||
s.notifyListeners(&event)
|
||||
}
|
||||
for _, event := range roomData.Timeline.Events {
|
||||
event.RoomID = roomID
|
||||
s.notifyListeners(&event)
|
||||
}
|
||||
}
|
||||
for roomID, roomData := range res.Rooms.Invite {
|
||||
room := s.getOrCreateRoom(roomID)
|
||||
for _, event := range roomData.State.Events {
|
||||
event.RoomID = roomID
|
||||
room.UpdateState(&event)
|
||||
s.notifyListeners(&event)
|
||||
}
|
||||
}
|
||||
for roomID, roomData := range res.Rooms.Leave {
|
||||
room := s.getOrCreateRoom(roomID)
|
||||
for _, event := range roomData.Timeline.Events {
|
||||
if event.StateKey != nil {
|
||||
event.RoomID = roomID
|
||||
room.UpdateState(&event)
|
||||
s.notifyListeners(&event)
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// OnEventType allows callers to be notified when there are new events for the given event type.
|
||||
// There are no duplicate checks.
|
||||
func (s *DefaultSyncer) OnEventType(eventType string, callback OnEventListener) {
|
||||
_, exists := s.listeners[eventType]
|
||||
if !exists {
|
||||
s.listeners[eventType] = []OnEventListener{}
|
||||
}
|
||||
s.listeners[eventType] = append(s.listeners[eventType], callback)
|
||||
}
|
||||
|
||||
// shouldProcessResponse returns true if the response should be processed. May modify the response to remove
|
||||
// stuff that shouldn't be processed.
|
||||
func (s *DefaultSyncer) shouldProcessResponse(resp *RespSync, since string) bool {
|
||||
if since == "" {
|
||||
return false
|
||||
}
|
||||
// This is a horrible hack because /sync will return the most recent messages for a room
|
||||
// as soon as you /join it. We do NOT want to process those events in that particular room
|
||||
// because they may have already been processed (if you toggle the bot in/out of the room).
|
||||
//
|
||||
// Work around this by inspecting each room's timeline and seeing if an m.room.member event for us
|
||||
// exists and is "join" and then discard processing that room entirely if so.
|
||||
// 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-- {
|
||||
e := roomData.Timeline.Events[i]
|
||||
if e.Type == "m.room.member" && e.StateKey != nil && *e.StateKey == s.UserID {
|
||||
m := e.Content["membership"]
|
||||
mship, ok := m.(string)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if mship == "join" {
|
||||
_, ok := resp.Rooms.Join[roomID]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
delete(resp.Rooms.Join, roomID) // don't re-process messages
|
||||
delete(resp.Rooms.Invite, roomID) // don't re-process invites
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// getOrCreateRoom must only be called by the Sync() goroutine which calls ProcessResponse()
|
||||
func (s *DefaultSyncer) getOrCreateRoom(roomID string) *Room {
|
||||
room := s.Store.LoadRoom(roomID)
|
||||
if room == nil { // create a new Room
|
||||
room = NewRoom(roomID)
|
||||
s.Store.SaveRoom(room)
|
||||
}
|
||||
return room
|
||||
}
|
||||
|
||||
func (s *DefaultSyncer) notifyListeners(event *Event) {
|
||||
listeners, exists := s.listeners[event.Type]
|
||||
if !exists {
|
||||
return
|
||||
}
|
||||
for _, fn := range listeners {
|
||||
fn(event)
|
||||
}
|
||||
}
|
||||
|
||||
// OnFailedSync always returns a 10 second wait period between failed /syncs, never a fatal error.
|
||||
func (s *DefaultSyncer) OnFailedSync(res *RespSync, err error) (time.Duration, error) {
|
||||
return 10 * time.Second, nil
|
||||
}
|
||||
|
||||
// GetFilterJSON returns a filter with a timeline limit of 50.
|
||||
func (s *DefaultSyncer) GetFilterJSON(userID string) json.RawMessage {
|
||||
return json.RawMessage(`{"room":{"timeline":{"limit":50}}}`)
|
||||
}
|
130
vendor/src/github.com/matrix-org/gomatrix/userids.go
vendored
Normal file
130
vendor/src/github.com/matrix-org/gomatrix/userids.go
vendored
Normal file
|
@ -0,0 +1,130 @@
|
|||
package gomatrix
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const lowerhex = "0123456789abcdef"
|
||||
|
||||
// encode the given byte using quoted-printable encoding (e.g "=2f")
|
||||
// and writes it to the buffer
|
||||
// See https://golang.org/src/mime/quotedprintable/writer.go
|
||||
func encode(buf *bytes.Buffer, b byte) {
|
||||
buf.WriteByte('=')
|
||||
buf.WriteByte(lowerhex[b>>4])
|
||||
buf.WriteByte(lowerhex[b&0x0f])
|
||||
}
|
||||
|
||||
// escape the given alpha character and writes it to the buffer
|
||||
func escape(buf *bytes.Buffer, b byte) {
|
||||
buf.WriteByte('_')
|
||||
if b == '_' {
|
||||
buf.WriteByte('_') // another _
|
||||
} else {
|
||||
buf.WriteByte(b + 0x20) // ASCII shift A-Z to a-z
|
||||
}
|
||||
}
|
||||
|
||||
func shouldEncode(b byte) bool {
|
||||
return b != '-' && b != '.' && b != '_' && !(b >= '0' && b <= '9') && !(b >= 'a' && b <= 'z') && !(b >= 'A' && b <= 'Z')
|
||||
}
|
||||
|
||||
func shouldEscape(b byte) bool {
|
||||
return (b >= 'A' && b <= 'Z') || b == '_'
|
||||
}
|
||||
|
||||
func isValidByte(b byte) bool {
|
||||
return isValidEscapedChar(b) || (b >= '0' && b <= '9') || b == '.' || b == '=' || b == '-'
|
||||
}
|
||||
|
||||
func isValidEscapedChar(b byte) bool {
|
||||
return b == '_' || (b >= 'a' && b <= 'z')
|
||||
}
|
||||
|
||||
// EncodeUserLocalpart encodes the given string into Matrix-compliant user ID localpart form.
|
||||
// See http://matrix.org/docs/spec/intro.html#mapping-from-other-character-sets
|
||||
//
|
||||
// This returns a string with only the characters "a-z0-9._=-". The uppercase range A-Z
|
||||
// are encoded using leading underscores ("_"). Characters outside the aforementioned ranges
|
||||
// (including literal underscores ("_") and equals ("=")) are encoded as UTF8 code points (NOT NCRs)
|
||||
// and converted to lower-case hex with a leading "=". For example:
|
||||
// Alph@Bet_50up => _alph=40_bet=5f50up
|
||||
func EncodeUserLocalpart(str string) string {
|
||||
strBytes := []byte(str)
|
||||
var outputBuffer bytes.Buffer
|
||||
for _, b := range strBytes {
|
||||
if shouldEncode(b) {
|
||||
encode(&outputBuffer, b)
|
||||
} else if shouldEscape(b) {
|
||||
escape(&outputBuffer, b)
|
||||
} else {
|
||||
outputBuffer.WriteByte(b)
|
||||
}
|
||||
}
|
||||
return outputBuffer.String()
|
||||
}
|
||||
|
||||
// DecodeUserLocalpart decodes the given string back into the original input string.
|
||||
// Returns an error if the given string is not a valid user ID localpart encoding.
|
||||
// See http://matrix.org/docs/spec/intro.html#mapping-from-other-character-sets
|
||||
//
|
||||
// This decodes quoted-printable bytes back into UTF8, and unescapes casing. For
|
||||
// example:
|
||||
// _alph=40_bet=5f50up => Alph@Bet_50up
|
||||
// Returns an error if the input string contains characters outside the
|
||||
// range "a-z0-9._=-", has an invalid quote-printable byte (e.g. not hex), or has
|
||||
// an invalid _ escaped byte (e.g. "_5").
|
||||
func DecodeUserLocalpart(str string) (string, error) {
|
||||
strBytes := []byte(str)
|
||||
var outputBuffer bytes.Buffer
|
||||
for i := 0; i < len(strBytes); i++ {
|
||||
b := strBytes[i]
|
||||
if !isValidByte(b) {
|
||||
return "", fmt.Errorf("Byte pos %d: Invalid byte", i)
|
||||
}
|
||||
|
||||
if b == '_' { // next byte is a-z and should be upper-case or is another _ and should be a literal _
|
||||
if i+1 >= len(strBytes) {
|
||||
return "", fmt.Errorf("Byte pos %d: expected _[a-z_] encoding but ran out of string", i)
|
||||
}
|
||||
if !isValidEscapedChar(strBytes[i+1]) { // invalid escaping
|
||||
return "", fmt.Errorf("Byte pos %d: expected _[a-z_] encoding", i)
|
||||
}
|
||||
if strBytes[i+1] == '_' {
|
||||
outputBuffer.WriteByte('_')
|
||||
} else {
|
||||
outputBuffer.WriteByte(strBytes[i+1] - 0x20) // ASCII shift a-z to A-Z
|
||||
}
|
||||
i++ // skip next byte since we just handled it
|
||||
} else if b == '=' { // next 2 bytes are hex and should be buffered ready to be read as utf8
|
||||
if i+2 >= len(strBytes) {
|
||||
return "", fmt.Errorf("Byte pos: %d: expected quote-printable encoding but ran out of string", i)
|
||||
}
|
||||
dst := make([]byte, 1)
|
||||
_, err := hex.Decode(dst, strBytes[i+1:i+3])
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
outputBuffer.WriteByte(dst[0])
|
||||
i += 2 // skip next 2 bytes since we just handled it
|
||||
} else { // pass through
|
||||
outputBuffer.WriteByte(b)
|
||||
}
|
||||
}
|
||||
return outputBuffer.String(), nil
|
||||
}
|
||||
|
||||
// ExtractUserLocalpart extracts the localpart portion of a user ID.
|
||||
// See http://matrix.org/docs/spec/intro.html#user-identifiers
|
||||
func ExtractUserLocalpart(userID string) (string, error) {
|
||||
if len(userID) == 0 || userID[0] != '@' {
|
||||
return "", fmt.Errorf("%s is not a valid user id", userID)
|
||||
}
|
||||
return strings.TrimPrefix(
|
||||
strings.SplitN(userID, ":", 2)[0], // @foo:bar:8448 => [ "@foo", "bar:8448" ]
|
||||
"@", // remove "@" prefix
|
||||
), nil
|
||||
}
|
27
vendor/src/github.com/matrix-org/gomatrix/userids_examples_test.go
vendored
Normal file
27
vendor/src/github.com/matrix-org/gomatrix/userids_examples_test.go
vendored
Normal file
|
@ -0,0 +1,27 @@
|
|||
package gomatrix
|
||||
|
||||
import "fmt"
|
||||
|
||||
func ExampleEncodeUserLocalpart() {
|
||||
localpart := EncodeUserLocalpart("Alph@Bet_50up")
|
||||
fmt.Println(localpart)
|
||||
// Output: _alph=40_bet__50up
|
||||
}
|
||||
|
||||
func ExampleDecodeUserLocalpart() {
|
||||
localpart, err := DecodeUserLocalpart("_alph=40_bet__50up")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println(localpart)
|
||||
// Output: Alph@Bet_50up
|
||||
}
|
||||
|
||||
func ExampleExtractUserLocalpart() {
|
||||
localpart, err := ExtractUserLocalpart("@alice:matrix.org")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println(localpart)
|
||||
// Output: alice
|
||||
}
|
86
vendor/src/github.com/matrix-org/gomatrix/userids_test.go
vendored
Normal file
86
vendor/src/github.com/matrix-org/gomatrix/userids_test.go
vendored
Normal file
|
@ -0,0 +1,86 @@
|
|||
package gomatrix
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
var useridtests = []struct {
|
||||
Input string
|
||||
Output string
|
||||
}{
|
||||
{"Alph@Bet_50up", "_alph=40_bet__50up"}, // The doc example
|
||||
{"abcdef", "abcdef"}, // no-op
|
||||
{"i_like_pie_", "i__like__pie__"}, // double underscore escaping
|
||||
{"ABCDEF", "_a_b_c_d_e_f"}, // all-caps
|
||||
{"!£", "=21=c2=a3"}, // punctuation and outside ascii range (U+00A3 => c2 a3)
|
||||
{"___", "______"}, // literal underscores
|
||||
{"hello-world.", "hello-world."}, // allowed punctuation
|
||||
{"5+5=10", "5=2b5=3d10"}, // equals sign
|
||||
{"東方Project", "=e6=9d=b1=e6=96=b9_project"}, // CJK mixed
|
||||
{" foo bar", "=09foo=20bar"}, // whitespace (tab and space)
|
||||
}
|
||||
|
||||
func TestEncodeUserLocalpart(t *testing.T) {
|
||||
for _, u := range useridtests {
|
||||
out := EncodeUserLocalpart(u.Input)
|
||||
if out != u.Output {
|
||||
t.Fatalf("TestEncodeUserLocalpart(%s) => Got: %s Expected: %s", u.Input, out, u.Output)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodeUserLocalpart(t *testing.T) {
|
||||
for _, u := range useridtests {
|
||||
in, _ := DecodeUserLocalpart(u.Output)
|
||||
if in != u.Input {
|
||||
t.Fatalf("TestDecodeUserLocalpart(%s) => Got: %s Expected: %s", u.Output, in, u.Input)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var errtests = []struct {
|
||||
Input string
|
||||
}{
|
||||
{"foo@bar"}, // invalid character @
|
||||
{"foo_5bar"}, // invalid character after _
|
||||
{"foo_._-bar"}, // multiple invalid characters after _
|
||||
{"foo=2Hbar"}, // invalid hex after =
|
||||
{"foo=2hbar"}, // invalid hex after = (lower-case)
|
||||
{"foo=======2fbar"}, // multiple invalid hex after =
|
||||
{"foo=2"}, // end of string after =
|
||||
{"foo_"}, // end of string after _
|
||||
}
|
||||
|
||||
func TestDecodeUserLocalpartErrors(t *testing.T) {
|
||||
for _, u := range errtests {
|
||||
out, err := DecodeUserLocalpart(u.Input)
|
||||
if out != "" {
|
||||
t.Fatalf("TestDecodeUserLocalpartErrors(%s) => Got: %s Expected: empty string", u.Input, out)
|
||||
}
|
||||
if err == nil {
|
||||
t.Fatalf("TestDecodeUserLocalpartErrors(%s) => Got: nil error Expected: error", u.Input)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var localparttests = []struct {
|
||||
Input string
|
||||
ExpectOutput string
|
||||
}{
|
||||
{"@foo:bar", "foo"},
|
||||
{"@foo:bar:8448", "foo"},
|
||||
{"@foo.bar:baz.quuz", "foo.bar"},
|
||||
}
|
||||
|
||||
func TestExtractUserLocalpart(t *testing.T) {
|
||||
for _, u := range localparttests {
|
||||
out, err := ExtractUserLocalpart(u.Input)
|
||||
if err != nil {
|
||||
t.Errorf("TestExtractUserLocalpart(%s) => Error: %s", u.Input, err)
|
||||
continue
|
||||
}
|
||||
if out != u.ExpectOutput {
|
||||
t.Errorf("TestExtractUserLocalpart(%s) => Got: %s, Want %s", u.Input, out, u.ExpectOutput)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -40,8 +40,16 @@ type UserInfo struct {
|
|||
|
||||
// NewClient makes a new Client
|
||||
func NewClient() *Client {
|
||||
return &Client{client: http.Client{Transport: newFederationTripper()}}
|
||||
}
|
||||
|
||||
type federationTripper struct {
|
||||
transport http.RoundTripper
|
||||
}
|
||||
|
||||
func newFederationTripper() *federationTripper {
|
||||
// TODO: Verify ceritificates
|
||||
tripper := federationTripper{
|
||||
return &federationTripper{
|
||||
transport: &http.Transport{
|
||||
// Set our own DialTLS function to avoid the default net/http SNI.
|
||||
// By default net/http and crypto/tls set the SNI to the target host.
|
||||
|
@ -66,14 +74,6 @@ func NewClient() *Client {
|
|||
},
|
||||
},
|
||||
}
|
||||
|
||||
return &Client{
|
||||
client: http.Client{Transport: &tripper},
|
||||
}
|
||||
}
|
||||
|
||||
type federationTripper struct {
|
||||
transport http.RoundTripper
|
||||
}
|
||||
|
||||
func makeHTTPSURL(u *url.URL, addr string) (httpsURL url.URL) {
|
||||
|
|
|
@ -43,6 +43,8 @@ type EventReference struct {
|
|||
}
|
||||
|
||||
// An EventBuilder is used to build a new event.
|
||||
// These can be exchanged between matrix servers in the federation APIs when
|
||||
// joining or leaving a room.
|
||||
type EventBuilder struct {
|
||||
// The user ID of the user sending the event.
|
||||
Sender string `json:"sender"`
|
||||
|
@ -60,20 +62,22 @@ type EventBuilder struct {
|
|||
Redacts string `json:"redacts,omitempty"`
|
||||
// The depth of the event, This should be one greater than the maximum depth of the previous events.
|
||||
// The create event has a depth of 1.
|
||||
Depth int64 `json:"depth"`
|
||||
content []byte
|
||||
unsigned []byte
|
||||
Depth int64 `json:"depth"`
|
||||
// The JSON object for "content" key of the event.
|
||||
Content rawJSON `json:"content"`
|
||||
// The JSON object for the "unsigned" key
|
||||
Unsigned rawJSON `json:"unsigned,omitempty"`
|
||||
}
|
||||
|
||||
// SetContent sets the JSON content key of the event.
|
||||
func (eb *EventBuilder) SetContent(content interface{}) (err error) {
|
||||
eb.content, err = json.Marshal(content)
|
||||
eb.Content, err = json.Marshal(content)
|
||||
return
|
||||
}
|
||||
|
||||
// SetUnsigned sets the JSON unsigned key of the event.
|
||||
func (eb *EventBuilder) SetUnsigned(unsigned interface{}) (err error) {
|
||||
eb.unsigned, err = json.Marshal(unsigned)
|
||||
eb.Unsigned, err = json.Marshal(unsigned)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -113,8 +117,6 @@ func (eb *EventBuilder) Build(eventID string, now time.Time, origin ServerName,
|
|||
var event struct {
|
||||
EventBuilder
|
||||
EventID string `json:"event_id"`
|
||||
RawContent rawJSON `json:"content"`
|
||||
RawUnsigned rawJSON `json:"unsigned,omitempty"`
|
||||
OriginServerTS Timestamp `json:"origin_server_ts"`
|
||||
Origin ServerName `json:"origin"`
|
||||
}
|
||||
|
@ -125,8 +127,6 @@ func (eb *EventBuilder) Build(eventID string, now time.Time, origin ServerName,
|
|||
if event.AuthEvents == nil {
|
||||
event.AuthEvents = emptyEventReferenceList
|
||||
}
|
||||
event.RawContent = rawJSON(event.content)
|
||||
event.RawUnsigned = rawJSON(event.unsigned)
|
||||
event.OriginServerTS = AsTimestamp(now)
|
||||
event.Origin = origin
|
||||
event.EventID = eventID
|
||||
|
|
|
@ -115,7 +115,7 @@ func StateNeededForEventBuilder(builder *EventBuilder) (result StateNeeded, err
|
|||
// Extract the 'content' object from the event if it is m.room.member as we need to know 'membership'
|
||||
var content *memberContent
|
||||
if builder.Type == "m.room.member" {
|
||||
if err = json.Unmarshal(builder.content, &content); err != nil {
|
||||
if err = json.Unmarshal(builder.Content, &content); err != nil {
|
||||
err = errorf("unparsable member event content: %s", err.Error())
|
||||
return
|
||||
}
|
||||
|
|
162
vendor/src/github.com/matrix-org/gomatrixserverlib/federationclient.go
vendored
Normal file
162
vendor/src/github.com/matrix-org/gomatrixserverlib/federationclient.go
vendored
Normal file
|
@ -0,0 +1,162 @@
|
|||
package gomatrixserverlib
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/matrix-org/gomatrix"
|
||||
"golang.org/x/crypto/ed25519"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// An FederationClient is a matrix federation client that adds
|
||||
// "Authorization: X-Matrix" headers to requests that need ed25519 signatures
|
||||
type FederationClient struct {
|
||||
Client
|
||||
serverName ServerName
|
||||
serverKeyID KeyID
|
||||
serverPrivateKey ed25519.PrivateKey
|
||||
}
|
||||
|
||||
// NewFederationClient makes a new FederationClient
|
||||
func NewFederationClient(
|
||||
serverName ServerName, keyID KeyID, privateKey ed25519.PrivateKey,
|
||||
) *FederationClient {
|
||||
return &FederationClient{
|
||||
Client: Client{client: http.Client{Transport: newFederationTripper()}},
|
||||
serverName: serverName,
|
||||
serverKeyID: keyID,
|
||||
serverPrivateKey: privateKey,
|
||||
}
|
||||
}
|
||||
|
||||
func (ac *FederationClient) doRequest(r FederationRequest, resBody interface{}) error {
|
||||
if err := r.Sign(ac.serverName, ac.serverKeyID, ac.serverPrivateKey); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req, err := r.HTTPRequest()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
res, err := ac.client.Do(req)
|
||||
if res != nil {
|
||||
defer res.Body.Close()
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
contents, err := ioutil.ReadAll(res.Body)
|
||||
if res.StatusCode/100 != 2 { // not 2xx
|
||||
// Adapted from https://github.com/matrix-org/gomatrix/blob/master/client.go
|
||||
var wrap error
|
||||
var respErr gomatrix.RespError
|
||||
if _ = json.Unmarshal(contents, &respErr); respErr.ErrCode != "" {
|
||||
wrap = respErr
|
||||
}
|
||||
|
||||
// If we failed to decode as RespError, don't just drop the HTTP body, include it in the
|
||||
// HTTP error instead (e.g proxy errors which return HTML).
|
||||
msg := "Failed to " + r.Method() + " JSON to " + r.RequestURI()
|
||||
if wrap == nil {
|
||||
msg = msg + ": " + string(contents)
|
||||
}
|
||||
|
||||
return gomatrix.HTTPError{
|
||||
Code: res.StatusCode,
|
||||
Message: msg,
|
||||
WrappedError: wrap,
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return json.Unmarshal(contents, resBody)
|
||||
}
|
||||
|
||||
// SendTransaction sends a transaction
|
||||
func (ac *FederationClient) SendTransaction(t Transaction) (res RespSend, err error) {
|
||||
path := "/_matrix/federation/v1/send/" + string(t.TransactionID) + "/"
|
||||
req := NewFederationRequest("PUT", t.Destination, path)
|
||||
if err = req.SetContent(t); err != nil {
|
||||
return
|
||||
}
|
||||
err = ac.doRequest(req, &res)
|
||||
return
|
||||
}
|
||||
|
||||
// MakeJoin makes a join m.room.member event for a room on a remote matrix server.
|
||||
// This is used to join a room the local server isn't a member of.
|
||||
// We need to query a remote server because if we aren't in the room we don't
|
||||
// know what to use for the "prev_events" in the join event.
|
||||
// The remote server should return us a m.room.member event for our local user
|
||||
// with the "prev_events" filled out.
|
||||
// If this successfully returns an acceptable event we will sign it with our
|
||||
// server's key and pass it to SendJoin.
|
||||
// See https://matrix.org/docs/spec/server_server/unstable.html#joining-rooms
|
||||
func (ac *FederationClient) MakeJoin(s ServerName, roomID, userID string) (res RespMakeJoin, err error) {
|
||||
path := "/_matrix/federation/v1/make_join/" +
|
||||
url.PathEscape(roomID) + "/" +
|
||||
url.PathEscape(userID)
|
||||
req := NewFederationRequest("GET", s, path)
|
||||
err = ac.doRequest(req, &res)
|
||||
return
|
||||
}
|
||||
|
||||
// SendJoin sends a join m.room.member event obtained using MakeJoin via a
|
||||
// remote matrix server.
|
||||
// This is used to join a room the local server isn't a member of.
|
||||
// See https://matrix.org/docs/spec/server_server/unstable.html#joining-rooms
|
||||
func (ac *FederationClient) SendJoin(s ServerName, event Event) (res RespSendJoin, err error) {
|
||||
path := "/_matrix/federation/v1/send_join/" +
|
||||
url.PathEscape(event.RoomID()) + "/" +
|
||||
url.PathEscape(event.EventID())
|
||||
req := NewFederationRequest("PUT", s, path)
|
||||
if err = req.SetContent(event); err != nil {
|
||||
return
|
||||
}
|
||||
err = ac.doRequest(req, &res)
|
||||
return
|
||||
}
|
||||
|
||||
// LookupState retrieves the room state for a room at an event from a
|
||||
// remote matrix server as full matrix events.
|
||||
func (ac *FederationClient) LookupState(s ServerName, roomID, eventID string) (res RespState, err error) {
|
||||
path := "/_matrix/federation/v1/state/" +
|
||||
url.PathEscape(roomID) +
|
||||
"/?event_id=" +
|
||||
url.QueryEscape(eventID)
|
||||
req := NewFederationRequest("GET", s, path)
|
||||
err = ac.doRequest(req, &res)
|
||||
return
|
||||
}
|
||||
|
||||
// LookupStateIDs retrieves the room state for a room at an event from a
|
||||
// remote matrix server as lists of matrix event IDs.
|
||||
func (ac *FederationClient) LookupStateIDs(s ServerName, roomID, eventID string) (res RespStateIDs, err error) {
|
||||
path := "/_matrix/federation/v1/state_ids/" +
|
||||
url.PathEscape(roomID) +
|
||||
"/?event_id=" +
|
||||
url.QueryEscape(eventID)
|
||||
req := NewFederationRequest("GET", s, path)
|
||||
err = ac.doRequest(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.
|
||||
// If the room alias doesn't exist on the remote server then a 404 gomatrix.HTTPError
|
||||
// is returned.
|
||||
func (ac *FederationClient) LookupRoomAlias(s ServerName, roomAlias string) (res RespDirectory, err error) {
|
||||
path := "/_matrix/federation/v1/query/directory?room_alias=" +
|
||||
url.QueryEscape(roomAlias)
|
||||
req := NewFederationRequest("GET", s, path)
|
||||
err = ac.doRequest(req, &res)
|
||||
return
|
||||
}
|
106
vendor/src/github.com/matrix-org/gomatrixserverlib/federationtypes.go
vendored
Normal file
106
vendor/src/github.com/matrix-org/gomatrixserverlib/federationtypes.go
vendored
Normal file
|
@ -0,0 +1,106 @@
|
|||
package gomatrixserverlib
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// A RespSend is the content of a response to PUT /_matrix/federation/v1/send/{txnID}/
|
||||
type RespSend struct {
|
||||
// Map of event ID to the result of processing that event.
|
||||
PDUs map[string]PDUResult `json:"pdus"`
|
||||
}
|
||||
|
||||
// A PDUResult is the result of processing a matrix room event.
|
||||
type PDUResult struct {
|
||||
// If not empty then this is a human readable description of a problem
|
||||
// encountered processing an event.
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
// A RespStateIDs is the content of a response to GET /_matrix/federation/v1/state_ids/{roomID}/{eventID}
|
||||
type RespStateIDs struct {
|
||||
// A list of state event IDs for the state of the room before the requested event.
|
||||
StateEventIDs []string `json:"pdu_ids"`
|
||||
// A list of event IDs needed to authenticate the state events.
|
||||
AuthEventIDs []string `json:"auth_chain_ids"`
|
||||
}
|
||||
|
||||
// A RespState is the content of a response to GET /_matrix/federation/v1/state/{roomID}/{eventID}
|
||||
type RespState struct {
|
||||
// A list of events giving the state of the room before the request event.
|
||||
StateEvents []Event `json:"pdus"`
|
||||
// A list of events needed to authenticate the state events.
|
||||
AuthEvents []Event `json:"auth_chain"`
|
||||
}
|
||||
|
||||
// A RespMakeJoin is the content of a response to GET /_matrix/federation/v1/make_join/{roomID}/{userID}
|
||||
type RespMakeJoin struct {
|
||||
// An incomplete m.room.member event for a user on the requesting server
|
||||
// generated by the responding server.
|
||||
// See https://matrix.org/docs/spec/server_server/unstable.html#joining-rooms
|
||||
JoinEvent EventBuilder `json:"event"`
|
||||
}
|
||||
|
||||
// A RespSendJoin is the content of a response to PUT /_matrix/federation/v1/send_join/{roomID}/{eventID}
|
||||
type RespSendJoin RespState
|
||||
|
||||
// MarshalJSON implements json.Marshaller
|
||||
func (r RespSendJoin) MarshalJSON() ([]byte, error) {
|
||||
// SendJoinResponses contain the same data as a StateResponse but are
|
||||
// formatted slightly differently on the wire:
|
||||
// 1) The "pdus" field is renamed to "state".
|
||||
// 2) The object is placed as the second element of a two element list
|
||||
// where the first element is the constant integer 200.
|
||||
//
|
||||
//
|
||||
// So a state response of:
|
||||
//
|
||||
// {"pdus": x, "auth_chain": y}
|
||||
//
|
||||
// Becomes:
|
||||
//
|
||||
// [200, {"state": x, "auth_chain": y}]
|
||||
//
|
||||
// (This protocol oddity is the result of a typo in the synapse matrix
|
||||
// server, and is preserved to maintain compatibility.)
|
||||
|
||||
return json.Marshal([]interface{}{200, respSendJoinFields{
|
||||
r.StateEvents, r.AuthEvents,
|
||||
}})
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements json.Unmarshaller
|
||||
func (r *RespSendJoin) UnmarshalJSON(data []byte) error {
|
||||
var tuple []rawJSON
|
||||
if err := json.Unmarshal(data, &tuple); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(tuple) != 2 {
|
||||
return fmt.Errorf("gomatrixserverlib: invalid send join response, invalid length: %d != 2", len(tuple))
|
||||
}
|
||||
var fields respSendJoinFields
|
||||
if err := json.Unmarshal(tuple[1], &fields); err != nil {
|
||||
return err
|
||||
}
|
||||
r.StateEvents = fields.StateEvents
|
||||
r.AuthEvents = fields.AuthEvents
|
||||
return nil
|
||||
}
|
||||
|
||||
type respSendJoinFields struct {
|
||||
StateEvents []Event `json:"state"`
|
||||
AuthEvents []Event `json:"auth_chain"`
|
||||
}
|
||||
|
||||
// A RespDirectory is the content of a response to GET /_matrix/federation/v1/query/directory
|
||||
// This is returned when looking up a room alias from a remote server.
|
||||
// See https://matrix.org/docs/spec/server_server/unstable.html#directory
|
||||
type RespDirectory struct {
|
||||
// The matrix room ID the room alias corresponds to.
|
||||
RoomID string `json:"room_id"`
|
||||
// A list of matrix servers that the directory server thinks could be used
|
||||
// to join the room. The joining server may need to try multiple servers
|
||||
// before it finds one that it can use to join the room.
|
||||
Servers []ServerName `json:"servers"`
|
||||
}
|
47
vendor/src/github.com/matrix-org/gomatrixserverlib/federationtypes_test.go
vendored
Normal file
47
vendor/src/github.com/matrix-org/gomatrixserverlib/federationtypes_test.go
vendored
Normal file
|
@ -0,0 +1,47 @@
|
|||
package gomatrixserverlib
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRespSendJoinMarshalJSON(t *testing.T) {
|
||||
inputData := `{"pdus":[],"auth_chain":[]}`
|
||||
var input RespState
|
||||
if err := json.Unmarshal([]byte(inputData), &input); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
gotBytes, err := json.Marshal(RespSendJoin(input))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
want := `[200,{"state":[],"auth_chain":[]}]`
|
||||
got := string(gotBytes)
|
||||
|
||||
if want != got {
|
||||
t.Errorf("json.Marshal(RespSendJoin(%q)): wanted %q, got %q", inputData, want, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRespSendJoinUnmarshalJSON(t *testing.T) {
|
||||
inputData := `[200,{"state":[],"auth_chain":[]}]`
|
||||
var input RespSendJoin
|
||||
if err := json.Unmarshal([]byte(inputData), &input); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
gotBytes, err := json.Marshal(RespState(input))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
want := `{"pdus":[],"auth_chain":[]}`
|
||||
got := string(gotBytes)
|
||||
|
||||
if want != got {
|
||||
t.Errorf("json.Marshal(RespSendJoin(%q)): wanted %q, got %q", inputData, want, got)
|
||||
}
|
||||
|
||||
}
|
31
vendor/src/github.com/matrix-org/gomatrixserverlib/transaction.go
vendored
Normal file
31
vendor/src/github.com/matrix-org/gomatrixserverlib/transaction.go
vendored
Normal file
|
@ -0,0 +1,31 @@
|
|||
package gomatrixserverlib
|
||||
|
||||
// A Transaction is used to push data from one matrix server to another matrix
|
||||
// server.
|
||||
type Transaction struct {
|
||||
// The ID of the transaction.
|
||||
TransactionID TransactionID `json:"transaction_id"`
|
||||
// The server that sent the transaction.
|
||||
Origin ServerName `json:"origin"`
|
||||
// The server that should receive the transaction.
|
||||
Destination ServerName `json:"destination"`
|
||||
// The millisecond posix timestamp on the origin server when the
|
||||
// transaction was created.
|
||||
OriginServerTS Timestamp `json:"origin_server_ts"`
|
||||
// The IDs of the most recent transactions sent by the origin server to
|
||||
// the destination server. Multiple transactions can be sent by the origin
|
||||
// server to the destination server in parallel so there may be more than
|
||||
// one previous transaction.
|
||||
PreviousIDs []TransactionID `json:"previous_ids"`
|
||||
// The room events pushed from the origin server to the destination server
|
||||
// by this transaction. The events should either be events that originate
|
||||
// on the origin server or be join m.room.member events.
|
||||
PDUs []Event `json:"pdus"`
|
||||
}
|
||||
|
||||
// A TransactionID identifies a transaction sent by a matrix server to another
|
||||
// matrix server. The ID must be unique amoungst the transactions sent from the
|
||||
// origin server to the destination, but doesn't have to be globally unique.
|
||||
// The ID must be safe to insert into a URL path segment. The ID should have a
|
||||
// format matching '^[0-9A-Za-z\-_]*$'
|
||||
type TransactionID string
|
Loading…
Reference in a new issue