9
0
mirror of https://github.com/Lomanic/presence-button-web synced 2024-11-23 22:17:30 +00:00

Compare commits

..

4 Commits

Author SHA1 Message Date
Lomanic
4675e76680 Use github.com/matrix-org/gomatrix to interact with matrix instead of custom code 2020-10-24 19:10:22 +02:00
Lomanic
32afdde63d Properly report JSON decoding error in init 2020-10-24 19:09:08 +02:00
Lomanic
006ab04b8c Properly check env variables in init 2020-10-24 19:05:51 +02:00
Lomanic
ee78e7cd15 Make this repo go modules aware 2020-10-24 14:44:04 +02:00
21 changed files with 2153 additions and 56 deletions

5
go.mod Normal file
View File

@ -0,0 +1,5 @@
module github.com/Lomanic/presence-button-web
go 1.12
require github.com/matrix-org/gomatrix v0.0.0-20200827122206-7dd5e2a05bcd

2
go.sum Normal file
View File

@ -0,0 +1,2 @@
github.com/matrix-org/gomatrix v0.0.0-20200827122206-7dd5e2a05bcd h1:xVrqJK3xHREMNjwjljkAUaadalWc0rRbmVuQatzmgwg=
github.com/matrix-org/gomatrix v0.0.0-20200827122206-7dd5e2a05bcd/go.mod h1:/gBX06Kw0exX1HrwmoBibFA98yBk/jxKpGVeyQbff+s=

87
main.go
View File

@ -1,18 +1,17 @@
package main
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"net/url"
"os"
"path/filepath"
"strconv"
"strings"
"time"
"github.com/matrix-org/gomatrix"
)
type Status struct {
@ -54,7 +53,8 @@ var (
// formerly https://www.flaticon.com/free-icon/closed_1234190, maybe try https://flaticons.net/customize.php?dir=Miscellaneous&icon=Closed.png without attribution
false: `<?xml version="1.0" ?><svg viewBox="0 0 128 128" xmlns="http://www.w3.org/2000/svg"><defs><style>.cls-1{fill:#f8991d;}.cls-2{fill:#fd0;}.cls-3{fill:#314967;}</style></defs><title/><g data-name="18 Closed Sign" id="_18_Closed_Sign"><rect class="cls-1" height="36" rx="6" ry="6" width="96" x="16" y="64"/><circle class="cls-2" cx="64" cy="28" r="6"/><path class="cls-3" d="M71.85,77.83a4.37,4.37,0,0,1,2.34,1,1.93,1.93,0,1,0,2.08-3.25A7.94,7.94,0,0,0,71.85,74c-3.13,0-5.55,2.06-5.55,4.58,0,2.74,2.64,4.21,5.35,4.58.55.11,2,.45,2.26,1,0,.57-1.38,1-2,1a5.25,5.25,0,0,1-2.79-1.16,1.93,1.93,0,1,0-2.42,3,8.73,8.73,0,0,0,5.23,2c3.12,0,5.88-2,5.88-4.82s-2.88-4.4-5.66-4.78c-1.6-.31-2-.77-2-.77C70.15,78.39,70.8,77.83,71.85,77.83Z"/><path class="cls-3" d="M40,76.34V87a2,2,0,0,0,2,2h5.83a2,2,0,0,0,0-4H44V76.34A2,2,0,0,0,40,76.34Z"/><path class="cls-3" d="M50,81.48A7.16,7.16,0,1,0,57.23,74,7.32,7.32,0,0,0,50,81.48Zm10.26,0c0,2.89-3.21,4.64-5.27,2.43-1.94-2-.71-5.86,2.2-5.86A3.29,3.29,0,0,1,60.3,81.48Z"/><path class="cls-3" d="M34.44,78.82a2,2,0,0,0,2.49-3.21A7.7,7.7,0,0,0,32.15,74a7.6,7.6,0,0,0-7.6,7.48h0c0,6.3,7.44,9.7,12.39,5.86a2,2,0,0,0-2.51-3.2,3.52,3.52,0,1,1,0-5.31Z"/><path class="cls-3" d="M87.69,78.35a2,2,0,0,0,0-4H81.8a2,2,0,0,0-2,2V87a2,2,0,0,0,1,1.75V89h6.83a2,2,0,0,0,0-4H83.81V83.65H87a2,2,0,0,0,0-4h-3.2V78.35Z"/><path class="cls-3" d="M103.09,81.64a7.29,7.29,0,0,0-7.28-7.28H93.69a2,2,0,0,0-2,2V87a2,2,0,0,0,2,2h2.13A7.31,7.31,0,0,0,103.09,81.64ZM95.81,85h-.12V78.35h.12C100.14,78.35,100.18,84.93,95.81,85Z"/><path class="cls-3" d="M92.73,98H22a4,4,0,0,1-4-4V70a4,4,0,0,1,4-4H42.55a2,2,0,0,0,0-4H32.83L59.93,34.89a8,8,0,0,0,8.13,0L81.71,48.54a2,2,0,0,0,2.83-2.83L70.88,32.06A8,8,0,0,0,64,20a2,2,0,0,0,0,4,4,4,0,0,1,2.79,6.86C63.58,34,58.24,30.08,60.51,26a2,2,0,0,0-3.49-2,8,8,0,0,0,.09,8L27.17,62H22a8,8,0,0,0-8,8V94a8,8,0,0,0,8,8H92.73A2,2,0,0,0,92.73,98Z"/><path class="cls-3" d="M106,62h-5.17L88.09,49.26a2,2,0,0,0-2.83,2.83L95.17,62H76a2,2,0,0,0,0,4h30a4,4,0,0,1,4,4V94a4,4,0,0,1-4,4H98.51a2,2,0,0,0,0,4H106a8,8,0,0,0,8-8V70A8,8,0,0,0,106,62Z"/><path class="cls-3" d="M70,62H49.22a2,2,0,0,0,0,4H70A2,2,0,0,0,70,62Z"/></g></svg>`,
}
db *os.File
db *os.File
matrix *gomatrix.Client
)
func init() {
@ -62,14 +62,32 @@ func init() {
if val, _ := strconv.Atoi(port); val > 0 {
config.PORT = port
}
config.MATRIXUSERNAME = os.Getenv("MATRIXUSERNAME")
config.MATRIXACCESSTOKEN = os.Getenv("MATRIXACCESSTOKEN")
config.MATRIXROOM = os.Getenv("MATRIXROOM")
config.MATRIXOPENINGMESSAGE = os.Getenv("MATRIXOPENINGMESSAGE")
config.MATRIXCLOSINGMESSAGE = os.Getenv("MATRIXCLOSINGMESSAGE")
config.MATRIXACCESSTOKEN = os.Getenv("MATRIXACCESSTOKEN")
config.MATRIXUSERNAME = os.Getenv("MATRIXUSERNAME")
config.ESPUSERNAME = os.Getenv("ESPUSERNAME")
config.ESPPASSWORD = os.Getenv("ESPPASSWORD")
if config.MATRIXUSERNAME == "" {
panic("MATRIXUSERNAME is empty")
}
if config.MATRIXACCESSTOKEN == "" {
panic("MATRIXACCESSTOKEN is empty")
}
var err error
matrix, err = gomatrix.NewClient(fmt.Sprintf("https://%s", config.MATRIXUSERNAME[strings.Index(config.MATRIXUSERNAME, ":")+1:]), config.MATRIXUSERNAME, config.MATRIXACCESSTOKEN)
if err != nil {
panic(fmt.Sprintf("error creating matrix client: %s", err))
}
if err != nil {
panic(err)
}
if config.MATRIXROOM == "" {
panic("MATRIXROOM is empty")
}
@ -79,17 +97,15 @@ func init() {
if config.MATRIXCLOSINGMESSAGE == "" {
panic("MATRIXCLOSINGMESSAGE is empty")
}
if config.MATRIXACCESSTOKEN == "" {
panic("MATRIXACCESSTOKEN is empty")
}
if config.MATRIXUSERNAME == "" {
panic("MATRIXUSERNAME is empty")
if config.ESPUSERNAME == "" {
panic("ESPUSERNAME is empty")
}
if config.ESPPASSWORD == "" {
panic("ESPPASSWORD is empty")
}
err := os.MkdirAll(filepath.Dir(dbPath), 0755)
err = os.MkdirAll(filepath.Dir(dbPath), 0755)
if err != nil {
panic(err)
}
@ -97,8 +113,7 @@ func init() {
if err != nil && !os.IsNotExist(err) {
panic(err)
}
d := json.NewDecoder(db)
d.Decode(&status)
err = json.NewDecoder(db).Decode(&status)
if err != nil {
fmt.Println("error unmarshalling db:", err)
}
@ -117,7 +132,7 @@ func checkClosure() {
// the Fuz is newly closed, notify on matrix and write file to survive reboot
// TODO: matrix msg
fmt.Println("the Fuz is newly closed, notify on matrix and write file to survive reboot")
err := sendMatrixMessage(config.MATRIXUSERNAME, config.MATRIXACCESSTOKEN, config.MATRIXROOM, config.MATRIXCLOSINGMESSAGE)
_, err := matrix.SendText(config.MATRIXROOM, config.MATRIXCLOSINGMESSAGE)
if err != nil {
fmt.Println("err:", err)
time.Sleep(10 * time.Second)
@ -203,7 +218,7 @@ func statusHandler(w http.ResponseWriter, r *http.Request) {
if status.FuzIsOpen && (status.LastOpened.Equal(status.LastClosed) || status.LastOpened.Before(status.LastClosed)) {
// the Fuz is newly opened, notify on matrix and write file to survive reboot
fmt.Println("the Fuz is newly opened, notify on matrix and write file to survive reboot")
err := sendMatrixMessage(config.MATRIXUSERNAME, config.MATRIXACCESSTOKEN, config.MATRIXROOM, config.MATRIXOPENINGMESSAGE)
_, err := matrix.SendText(config.MATRIXROOM, config.MATRIXOPENINGMESSAGE)
if err != nil {
fmt.Println("err:", err)
return
@ -218,46 +233,6 @@ func statusHandler(w http.ResponseWriter, r *http.Request) {
}
}
func sendMatrixMessage(username, accessToken, room, messageText string) error {
type Message struct {
Msgtype string `json:"msgtype"`
Body string `json:"body"`
}
client := &http.Client{}
message := Message{
Msgtype: "m.text",
Body: messageText,
}
body := new(bytes.Buffer)
err := json.NewEncoder(body).Encode(message)
if err != nil {
return err
}
v := url.Values{}
v.Set("access_token", accessToken)
v.Set("limit", "1")
url := url.URL{
Scheme: "https",
Host: username[strings.Index(username, ":")+1:],
Path: fmt.Sprintf("/_matrix/client/r0/rooms/%s/send/m.room.message/%d", room, time.Now().UnixNano()/1000000),
RawQuery: v.Encode(),
}
req, err := http.NewRequest(http.MethodPut, url.String(), body)
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/json")
res, err := client.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
resBody, _ := ioutil.ReadAll(res.Body)
fmt.Println(string(resBody))
return nil
}
func main() {
go updateUptime()
go checkClosure()

24
vendor/github.com/matrix-org/gomatrix/.gitignore generated vendored Normal file
View File

@ -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

21
vendor/github.com/matrix-org/gomatrix/.golangci.yml generated vendored Normal file
View File

@ -0,0 +1,21 @@
run:
timeout: 5m
linters:
enable:
- vet
- vetshadow
- typecheck
- deadcode
- gocyclo
- golint
- varcheck
- structcheck
- maligned
- ineffassign
- misspell
- unparam
- goimports
- goconst
- unconvert
- errcheck
- interfacer

7
vendor/github.com/matrix-org/gomatrix/.travis.yml generated vendored Normal file
View File

@ -0,0 +1,7 @@
language: go
go:
- 1.13.10
install:
- go get github.com/golangci/golangci-lint/cmd/golangci-lint@v1.24.0
- go build
script: ./hooks/pre-commit

201
vendor/github.com/matrix-org/gomatrix/LICENSE generated vendored Normal file
View 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/github.com/matrix-org/gomatrix/README.md generated vendored Normal file
View 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.**

794
vendor/github.com/matrix-org/gomatrix/client.go generated vendored Normal file
View File

@ -0,0 +1,794 @@
// 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"
"strings"
"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 {
Contents []byte
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("contents=%v msg=%s code=%d wrapped=%s", e.Contents, 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 := append([]string{cli.Prefix}, urlPath...)
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...)
// Manually add the trailing slash back to the end of the path if it's explicitly needed
if strings.HasSuffix(urlPath[len(urlPath)-1], "/") {
hsURL.Path = hsURL.Path + "/"
}
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.
// The response body will be stream decoded into an interface. This will automatically stop if the response
// body is nil.
//
// 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, byte contents of the response body 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{}) error {
var req *http.Request
var err error
if reqBody != nil {
buf := new(bytes.Buffer)
if err := json.NewEncoder(buf).Encode(reqBody); err != nil {
return err
}
req, err = http.NewRequest(method, httpURL, buf)
} else {
req, err = http.NewRequest(method, httpURL, nil)
}
if err != nil {
return 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 err
}
if res.StatusCode/100 != 2 { // not 2xx
contents, err := ioutil.ReadAll(res.Body)
if err != nil {
return err
}
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 HTTPError{
Contents: contents,
Code: res.StatusCode,
Message: msg,
WrappedError: wrap,
}
}
if resBody != nil && res.Body != nil {
return json.NewDecoder(res.Body).Decode(&resBody)
}
return 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) {
err = cli.MakeRequest("POST", u, req, &resp)
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(httpErr.Contents, &uiaResp)
return
}
}
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.6.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
}
// LogoutAll logs the current user out on all devices. See https://matrix.org/docs/spec/client_server/r0.6.0#post-matrix-client-r0-logout-all
// This does not clear the credentials from the client instance. See ClearCredentails() instead.
func (cli *Client) LogoutAll() (resp *RespLogoutAll, err error) {
urlPath := cli.BuildURL("logout/all")
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
}
// PublicRooms returns the list of public rooms on target server. See https://matrix.org/docs/spec/client_server/r0.6.0#get-matrix-client-unstable-publicrooms
func (cli *Client) PublicRooms(limit int, since string, server string) (resp *RespPublicRooms, err error) {
args := map[string]string{}
if limit != 0 {
args["limit"] = strconv.Itoa(limit)
}
if since != "" {
args["since"] = since
}
if server != "" {
args["server"] = server
}
urlPath := cli.BuildURLWithQuery([]string{"publicRooms"}, args)
err = cli.MakeRequest("GET", urlPath, nil, &resp)
return
}
// PublicRoomsFiltered returns a subset of PublicRooms filtered server side.
// See https://matrix.org/docs/spec/client_server/r0.6.0#post-matrix-client-unstable-publicrooms
func (cli *Client) PublicRoomsFiltered(limit int, since string, server string, filter string) (resp *RespPublicRooms, err error) {
content := map[string]string{}
if limit != 0 {
content["limit"] = strconv.Itoa(limit)
}
if since != "" {
content["since"] = since
}
if filter != "" {
content["filter"] = filter
}
var urlPath string
if server == "" {
urlPath = cli.BuildURL("publicRooms")
} else {
urlPath = cli.BuildURLWithQuery([]string{"publicRooms"}, map[string]string{
"server": server,
})
}
err = cli.MakeRequest("POST", urlPath, content, &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() (string, 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) 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
}
// GetStatus returns the status of the user from the specified MXID. See https://matrix.org/docs/spec/client_server/r0.6.0#get-matrix-client-r0-presence-userid-status
func (cli *Client) GetStatus(mxid string) (resp *RespUserStatus, err error) {
urlPath := cli.BuildURL("presence", mxid, "status")
err = cli.MakeRequest("GET", urlPath, nil, &resp)
return
}
// GetOwnStatus returns the user's status. See https://matrix.org/docs/spec/client_server/r0.6.0#get-matrix-client-r0-presence-userid-status
func (cli *Client) GetOwnStatus() (resp *RespUserStatus, err error) {
return cli.GetStatus(cli.UserID)
}
// SetStatus sets the user's status. See https://matrix.org/docs/spec/client_server/r0.6.0#put-matrix-client-r0-presence-userid-status
func (cli *Client) SetStatus(presence, status string) (err error) {
urlPath := cli.BuildURL("presence", cli.UserID, "status")
s := struct {
Presence string `json:"presence"`
StatusMsg string `json:"status_msg"`
}{presence, status}
err = cli.MakeRequest("PUT", urlPath, &s, nil)
return
}
// 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{MsgType: "m.text", Body: text})
}
// SendFormattedText sends an m.room.message event into the given room with a msgtype of m.text, supports a subset of HTML for formatting.
// See https://matrix.org/docs/spec/client_server/r0.6.0#m-text
func (cli *Client) SendFormattedText(roomID, text, formattedText string) (*RespSendEvent, error) {
return cli.SendMessageEvent(roomID, "m.room.message",
TextMessage{MsgType: "m.text", Body: text, FormattedBody: formattedText, Format: "org.matrix.custom.html"})
}
// 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{MsgType: "m.notice", Body: 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
}
// MarkRead marks eventID in roomID as read, signifying the event, and all before it have been read. See https://matrix.org/docs/spec/client_server/r0.6.0#post-matrix-client-r0-rooms-roomid-receipt-receipttype-eventid
func (cli *Client) MarkRead(roomID, eventID string) error {
urlPath := cli.BuildURL("rooms", roomID, "receipt", "m.read", eventID)
return cli.MakeRequest("POST", urlPath, nil, nil)
}
// 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, req, &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{
Contents: contents,
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
}

157
vendor/github.com/matrix-org/gomatrix/events.go generated vendored Normal file
View File

@ -0,0 +1,157 @@
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)
Redacts string `json:"redacts,omitempty"` // The event ID that was redacted if a m.room.redaction event
Unsigned map[string]interface{} `json:"unsigned"` // The unsigned portions of the event, such as age and prev_content
Content map[string]interface{} `json:"content"` // The JSON content of the event.
PrevContent map[string]interface{} `json:"prev_content,omitempty"` // The JSON prev_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"`
FormattedBody string `json:"formatted_body"`
Format string `json:"format"`
}
// ThumbnailInfo contains info about an thumbnail image - http://matrix.org/docs/spec/client_server/r0.2.0.html#m-image
type ThumbnailInfo struct {
Height uint `json:"h,omitempty"`
Width uint `json:"w,omitempty"`
Mimetype string `json:"mimetype,omitempty"`
Size uint `json:"size,omitempty"`
}
// 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"`
ThumbnailInfo ThumbnailInfo `json:"thumbnail_info,omitempty"`
ThumbnailURL string `json:"thumbnail_url,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 ThumbnailInfo `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"`
}
// FileInfo contains info about an file - http://matrix.org/docs/spec/client_server/r0.2.0.html#m-file
type FileInfo struct {
Mimetype string `json:"mimetype,omitempty"`
Size uint `json:"size,omitempty"` //filesize in bytes
}
// FileMessage is an m.file event - http://matrix.org/docs/spec/client_server/r0.2.0.html#m-file
type FileMessage struct {
MsgType string `json:"msgtype"`
Body string `json:"body"`
URL string `json:"url"`
Filename string `json:"filename"`
Info FileInfo `json:"info,omitempty"`
ThumbnailURL string `json:"thumbnail_url,omitempty"`
ThumbnailInfo ImageInfo `json:"thumbnail_info,omitempty"`
}
// LocationMessage is an m.location event - http://matrix.org/docs/spec/client_server/r0.2.0.html#m-location
type LocationMessage struct {
MsgType string `json:"msgtype"`
Body string `json:"body"`
GeoURI string `json:"geo_uri"`
ThumbnailURL string `json:"thumbnail_url,omitempty"`
ThumbnailInfo ImageInfo `json:"thumbnail_info,omitempty"`
}
// AudioInfo contains info about an file - http://matrix.org/docs/spec/client_server/r0.2.0.html#m-audio
type AudioInfo struct {
Mimetype string `json:"mimetype,omitempty"`
Size uint `json:"size,omitempty"` //filesize in bytes
Duration uint `json:"duration,omitempty"` //audio duration in ms
}
// AudioMessage is an m.audio event - http://matrix.org/docs/spec/client_server/r0.2.0.html#m-audio
type AudioMessage struct {
MsgType string `json:"msgtype"`
Body string `json:"body"`
URL string `json:"url"`
Info AudioInfo `json:"info,omitempty"`
}
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,
}
}

90
vendor/github.com/matrix-org/gomatrix/filter.go generated vendored Normal file
View File

@ -0,0 +1,90 @@
// 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
import "errors"
//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 RoomFilter `json:"room,omitempty"`
}
// RoomFilter is used to define filtering rules for room events
type RoomFilter 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"`
}
// FilterPart is used to define filtering rules for specific categories of events
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"`
ContainsURL *bool `json:"contains_url,omitempty"`
}
// Validate checks if the filter contains valid property values
func (filter *Filter) Validate() error {
if filter.EventFormat != "client" && filter.EventFormat != "federation" {
return errors.New("Bad event_format value. Must be one of [\"client\", \"federation\"]")
}
return nil
}
// DefaultFilter returns the default filter used by the Matrix server if no filter is provided in the request
func DefaultFilter() Filter {
return Filter{
AccountData: DefaultFilterPart(),
EventFields: nil,
EventFormat: "client",
Presence: DefaultFilterPart(),
Room: RoomFilter{
AccountData: DefaultFilterPart(),
Ephemeral: DefaultFilterPart(),
IncludeLeave: false,
NotRooms: nil,
Rooms: nil,
State: DefaultFilterPart(),
Timeline: DefaultFilterPart(),
},
}
}
// DefaultFilterPart returns the default filter part used by the Matrix server if no filter is provided in the request
func DefaultFilterPart() FilterPart {
return FilterPart{
NotRooms: nil,
Rooms: nil,
Limit: 20,
NotSenders: nil,
NotTypes: nil,
Senders: nil,
Types: nil,
}
}

3
vendor/github.com/matrix-org/gomatrix/go.mod generated vendored Normal file
View File

@ -0,0 +1,3 @@
module github.com/matrix-org/gomatrix
go 1.12

69
vendor/github.com/matrix-org/gomatrix/identifier.go generated vendored Normal file
View File

@ -0,0 +1,69 @@
package gomatrix
// Identifier is the interface for https://matrix.org/docs/spec/client_server/r0.6.0#identifier-types
type Identifier interface {
// Returns the identifier type
// https://matrix.org/docs/spec/client_server/r0.6.0#identifier-types
Type() string
}
// UserIdentifier is the Identifier for https://matrix.org/docs/spec/client_server/r0.6.0#matrix-user-id
type UserIdentifier struct {
IDType string `json:"type"` // Set by NewUserIdentifer
User string `json:"user"`
}
// Type implements the Identifier interface
func (i UserIdentifier) Type() string {
return "m.id.user"
}
// NewUserIdentifier creates a new UserIdentifier with IDType set to "m.id.user"
func NewUserIdentifier(user string) UserIdentifier {
return UserIdentifier{
IDType: "m.id.user",
User: user,
}
}
// ThirdpartyIdentifier is the Identifier for https://matrix.org/docs/spec/client_server/r0.6.0#third-party-id
type ThirdpartyIdentifier struct {
IDType string `json:"type"` // Set by NewThirdpartyIdentifier
Medium string `json:"medium"`
Address string `json:"address"`
}
// Type implements the Identifier interface
func (i ThirdpartyIdentifier) Type() string {
return "m.id.thirdparty"
}
// NewThirdpartyIdentifier creates a new UserIdentifier with IDType set to "m.id.user"
func NewThirdpartyIdentifier(medium, address string) ThirdpartyIdentifier {
return ThirdpartyIdentifier{
IDType: "m.id.thirdparty",
Medium: medium,
Address: address,
}
}
// PhoneIdentifier is the Identifier for https://matrix.org/docs/spec/client_server/r0.6.0#phone-number
type PhoneIdentifier struct {
IDType string `json:"type"` // Set by NewPhoneIdentifier
Country string `json:"country"`
Phone string `json:"phone"`
}
// Type implements the Identifier interface
func (i PhoneIdentifier) Type() string {
return "m.id.phone"
}
// NewPhoneIdentifier creates a new UserIdentifier with IDType set to "m.id.user"
func NewPhoneIdentifier(country, phone string) PhoneIdentifier {
return PhoneIdentifier{
IDType: "m.id.phone",
Country: country,
Phone: phone,
}
}

79
vendor/github.com/matrix-org/gomatrix/requests.go generated vendored Normal file
View File

@ -0,0 +1,79 @@
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.6.0.html#post-matrix-client-r0-login
type ReqLogin struct {
Type string `json:"type"`
Identifier Identifier `json:"identifier,omitempty"`
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"`
}

210
vendor/github.com/matrix-org/gomatrix/responses.go generated vendored Normal file
View File

@ -0,0 +1,210 @@
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"`
}
// RespPublicRooms is the JSON response for http://matrix.org/speculator/spec/HEAD/client_server/unstable.html#get-matrix-client-unstable-publicrooms
type RespPublicRooms struct {
TotalRoomCountEstimate int `json:"total_room_count_estimate"`
PrevBatch string `json:"prev_batch"`
NextBatch string `json:"next_batch"`
Chunk []PublicRoom `json:"chunk"`
}
// 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:"session"`
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"`
}
// RespUserStatus is the JSON response for https://matrix.org/docs/spec/client_server/r0.6.0#get-matrix-client-r0-presence-userid-status
type RespUserStatus struct {
Presence string `json:"presence"`
StatusMsg string `json:"status_msg"`
LastActiveAgo int `json:"last_active_ago"`
CurrentlyActive bool `json:"currently_active"`
}
// 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.6.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"`
WellKnown DiscoveryInformation `json:"well_known"`
}
// DiscoveryInformation is the JSON Response for https://matrix.org/docs/spec/client_server/r0.6.0#get-well-known-matrix-client and a part of the JSON Response for https://matrix.org/docs/spec/client_server/r0.6.0#post-matrix-client-r0-login
type DiscoveryInformation struct {
Homeserver struct {
BaseURL string `json:"base_url"`
} `json:"m.homeserver"`
IdentityServer struct {
BaseURL string `json:"base_url"`
} `json:"m.identitiy_server"`
}
// RespLogout is the JSON response for http://matrix.org/docs/spec/client_server/r0.6.0.html#post-matrix-client-r0-logout
type RespLogout struct{}
// RespLogoutAll is the JSON response for https://matrix.org/docs/spec/client_server/r0.6.0#post-matrix-client-r0-logout-all
type RespLogoutAll 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"`
} `json:"join"`
Invite map[string]struct {
State struct {
Events []Event
} `json:"invite_state"`
} `json:"invite"`
} `json:"rooms"`
}
// RespTurnServer is the JSON response from a Turn Server
type RespTurnServer struct {
Username string `json:"username"`
Password string `json:"password"`
TTL int `json:"ttl"`
URIs []string `json:"uris"`
}

63
vendor/github.com/matrix-org/gomatrix/room.go generated vendored Normal file
View File

@ -0,0 +1,63 @@
package gomatrix
// Room represents a single Matrix room.
type Room struct {
ID string
State map[string]map[string]*Event
}
// PublicRoom represents the information about a public room obtainable from the room directory
type PublicRoom struct {
CanonicalAlias string `json:"canonical_alias"`
Name string `json:"name"`
WorldReadable bool `json:"world_readable"`
Topic string `json:"topic"`
NumJoinedMembers int `json:"num_joined_members"`
AvatarURL string `json:"avatar_url"`
RoomID string `json:"room_id"`
GuestCanJoin bool `json:"guest_can_join"`
Aliases []string `json:"aliases"`
}
// 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/github.com/matrix-org/gomatrix/store.go generated vendored Normal file
View 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),
}
}

168
vendor/github.com/matrix-org/gomatrix/sync.go generated vendored Normal file
View File

@ -0,0 +1,168 @@
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 _, event := range roomData.Ephemeral.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}}}`)
}

26
vendor/github.com/matrix-org/gomatrix/tags.go generated vendored Normal file
View File

@ -0,0 +1,26 @@
// Copyright 2019 Sumukha PK
//
// 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
// TagContent contains the data for an m.tag message type
// https://matrix.org/docs/spec/client_server/r0.4.0.html#m-tag
type TagContent struct {
Tags map[string]TagProperties `json:"tags"`
}
// TagProperties contains the properties of a Tag
type TagProperties struct {
Order float32 `json:"order,omitempty"` // Empty values must be neglected
}

130
vendor/github.com/matrix-org/gomatrix/userids.go generated vendored Normal file
View 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
}

2
vendor/modules.txt vendored Normal file
View File

@ -0,0 +1,2 @@
# github.com/matrix-org/gomatrix v0.0.0-20200827122206-7dd5e2a05bcd
github.com/matrix-org/gomatrix