aboutsummaryrefslogtreecommitdiff
path: root/vendor/maunium.net/go/gomatrix
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/maunium.net/go/gomatrix')
-rw-r--r--vendor/maunium.net/go/gomatrix/.gitignore24
-rw-r--r--vendor/maunium.net/go/gomatrix/.travis.yml9
-rw-r--r--vendor/maunium.net/go/gomatrix/LICENSE201
-rw-r--r--vendor/maunium.net/go/gomatrix/README.md6
-rw-r--r--vendor/maunium.net/go/gomatrix/client.go703
-rw-r--r--vendor/maunium.net/go/gomatrix/events.go109
-rw-r--r--vendor/maunium.net/go/gomatrix/filter.go43
-rw-r--r--vendor/maunium.net/go/gomatrix/requests.go78
-rw-r--r--vendor/maunium.net/go/gomatrix/responses.go182
-rw-r--r--vendor/maunium.net/go/gomatrix/room.go50
-rw-r--r--vendor/maunium.net/go/gomatrix/store.go65
-rw-r--r--vendor/maunium.net/go/gomatrix/sync.go164
-rw-r--r--vendor/maunium.net/go/gomatrix/userids.go130
13 files changed, 1764 insertions, 0 deletions
diff --git a/vendor/maunium.net/go/gomatrix/.gitignore b/vendor/maunium.net/go/gomatrix/.gitignore
new file mode 100644
index 0000000..daf913b
--- /dev/null
+++ b/vendor/maunium.net/go/gomatrix/.gitignore
@@ -0,0 +1,24 @@
+# Compiled Object files, Static and Dynamic libs (Shared Objects)
+*.o
+*.a
+*.so
+
+# Folders
+_obj
+_test
+
+# Architecture specific extensions/prefixes
+*.[568vq]
+[568vq].out
+
+*.cgo1.go
+*.cgo2.c
+_cgo_defun.c
+_cgo_gotypes.go
+_cgo_export.*
+
+_testmain.go
+
+*.exe
+*.test
+*.prof
diff --git a/vendor/maunium.net/go/gomatrix/.travis.yml b/vendor/maunium.net/go/gomatrix/.travis.yml
new file mode 100644
index 0000000..fadc326
--- /dev/null
+++ b/vendor/maunium.net/go/gomatrix/.travis.yml
@@ -0,0 +1,9 @@
+language: go
+go:
+ - 1.8
+install:
+ - go get github.com/golang/lint/golint
+ - go get github.com/fzipp/gocyclo
+ - go get github.com/client9/misspell/...
+ - go get github.com/gordonklaus/ineffassign
+script: ./hooks/pre-commit
diff --git a/vendor/maunium.net/go/gomatrix/LICENSE b/vendor/maunium.net/go/gomatrix/LICENSE
new file mode 100644
index 0000000..8dada3e
--- /dev/null
+++ b/vendor/maunium.net/go/gomatrix/LICENSE
@@ -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.
diff --git a/vendor/maunium.net/go/gomatrix/README.md b/vendor/maunium.net/go/gomatrix/README.md
new file mode 100644
index 0000000..ea9109a
--- /dev/null
+++ b/vendor/maunium.net/go/gomatrix/README.md
@@ -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.**
diff --git a/vendor/maunium.net/go/gomatrix/client.go b/vendor/maunium.net/go/gomatrix/client.go
new file mode 100644
index 0000000..90a07c6
--- /dev/null
+++ b/vendor/maunium.net/go/gomatrix/client.go
@@ -0,0 +1,703 @@
+// 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 parameters 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
+}
+
+// GetDisplayName returns the display name of the user from the specified MXID. See https://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-profile-userid-displayname
+func (cli *Client) GetDisplayName(mxid string) (resp *RespUserDisplayName, err error) {
+ urlPath := cli.BuildURL("profile", mxid, "displayname")
+ _, err = cli.MakeRequest("GET", urlPath, nil, &resp)
+ return
+}
+
+// GetOwnDisplayName returns the user's display name. See https://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-profile-userid-displayname
+func (cli *Client) GetOwnDisplayName() (resp *RespUserDisplayName, err error) {
+ urlPath := cli.BuildURL("profile", cli.UserID, "displayname")
+ _, err = cli.MakeRequest("GET", urlPath, nil, &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"] = strconv.Itoa(limit)
+ }
+
+ urlPath := cli.BuildURLWithQuery([]string{"rooms", roomID, "messages"}, query)
+ _, err = cli.MakeRequest("GET", urlPath, nil, &resp)
+ return
+}
+
+// TurnServer returns turn server details and credentials for the client to use when initiating calls.
+// See http://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-voip-turnserver
+func (cli *Client) TurnServer() (resp *RespTurnServer, err error) {
+ urlPath := cli.BuildURL("voip", "turnServer")
+ _, 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
+}
diff --git a/vendor/maunium.net/go/gomatrix/events.go b/vendor/maunium.net/go/gomatrix/events.go
new file mode 100644
index 0000000..231dbdd
--- /dev/null
+++ b/vendor/maunium.net/go/gomatrix/events.go
@@ -0,0 +1,109 @@
+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 int64 `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.
+ Unsigned Unsigned `json:"unsigned,omitempty"` // Unsigned content set by own homeserver.
+}
+
+type Unsigned struct {
+ PrevContent map[string]interface{} `json:"prev_content,omitempty"`
+ PrevSender string `json:"prev_sender,omitempty"`
+ ReplacesState string `json:"replaces_state,omitempty"`
+ Age int64 `json:"age"`
+}
+
+// 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,
+ }
+}
diff --git a/vendor/maunium.net/go/gomatrix/filter.go b/vendor/maunium.net/go/gomatrix/filter.go
new file mode 100644
index 0000000..e4e7628
--- /dev/null
+++ b/vendor/maunium.net/go/gomatrix/filter.go
@@ -0,0 +1,43 @@
+// Copyright 2017 Jan Christian Grünhage
+//
+// 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.
+
+package gomatrix
+
+//Filter is used by clients to specify how the server should filter responses to e.g. sync requests
+//Specified by: https://matrix.org/docs/spec/client_server/r0.2.0.html#filtering
+type Filter struct {
+ AccountData FilterPart `json:"account_data,omitempty"`
+ EventFields []string `json:"event_fields,omitempty"`
+ EventFormat string `json:"event_format,omitempty"`
+ Presence FilterPart `json:"presence,omitempty"`
+ Room struct {
+ AccountData FilterPart `json:"account_data,omitempty"`
+ Ephemeral FilterPart `json:"ephemeral,omitempty"`
+ IncludeLeave bool `json:"include_leave,omitempty"`
+ NotRooms []string `json:"not_rooms,omitempty"`
+ Rooms []string `json:"rooms,omitempty"`
+ State FilterPart `json:"state,omitempty"`
+ Timeline FilterPart `json:"timeline,omitempty"`
+ } `json:"room,omitempty"`
+}
+
+type FilterPart struct {
+ NotRooms []string `json:"not_rooms,omitempty"`
+ Rooms []string `json:"rooms,omitempty"`
+ Limit *int `json:"limit,omitempty"`
+ NotSenders []string `json:"not_senders,omitempty"`
+ NotTypes []string `json:"not_types,omitempty"`
+ Senders []string `json:"senders,omitempty"`
+ Types []string `json:"types,omitempty"`
+}
diff --git a/vendor/maunium.net/go/gomatrix/requests.go b/vendor/maunium.net/go/gomatrix/requests.go
new file mode 100644
index 0000000..af99a22
--- /dev/null
+++ b/vendor/maunium.net/go/gomatrix/requests.go
@@ -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"`
+}
diff --git a/vendor/maunium.net/go/gomatrix/responses.go b/vendor/maunium.net/go/gomatrix/responses.go
new file mode 100644
index 0000000..6d43bd3
--- /dev/null
+++ b/vendor/maunium.net/go/gomatrix/responses.go
@@ -0,0 +1,182 @@
+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
+}
+
+// RespUserDisplayName is the JSON response for https://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-profile-userid-displayname
+type RespUserDisplayName struct {
+ DisplayName string `json:"displayname"`
+}
+
+// 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"`
+ Ephemeral struct {
+ Events []*Event `json:"events"`
+ } `json:"ephemeral"`
+ AccountData struct {
+ Events []*Event `json:"events"`
+ } `json:"account_data"`
+ } `json:"join"`
+ Invite map[string]struct {
+ State struct {
+ Events []*Event `json:"events"`
+ } `json:"invite_state"`
+ } `json:"invite"`
+ } `json:"rooms"`
+}
+
+type RespTurnServer struct {
+ Username string `json:"username"`
+ Password string `json:"password"`
+ TTL int `json:"ttl"`
+ URIs []string `json:"uris"`
+}
diff --git a/vendor/maunium.net/go/gomatrix/room.go b/vendor/maunium.net/go/gomatrix/room.go
new file mode 100644
index 0000000..c9b2351
--- /dev/null
+++ b/vendor/maunium.net/go/gomatrix/room.go
@@ -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),
+ }
+}
diff --git a/vendor/maunium.net/go/gomatrix/store.go b/vendor/maunium.net/go/gomatrix/store.go
new file mode 100644
index 0000000..6dc687e
--- /dev/null
+++ b/vendor/maunium.net/go/gomatrix/store.go
@@ -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),
+ }
+}
diff --git a/vendor/maunium.net/go/gomatrix/sync.go b/vendor/maunium.net/go/gomatrix/sync.go
new file mode 100644
index 0000000..e1233a4
--- /dev/null
+++ b/vendor/maunium.net/go/gomatrix/sync.go
@@ -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}}}`)
+}
diff --git a/vendor/maunium.net/go/gomatrix/userids.go b/vendor/maunium.net/go/gomatrix/userids.go
new file mode 100644
index 0000000..23e7807
--- /dev/null
+++ b/vendor/maunium.net/go/gomatrix/userids.go
@@ -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
+}