summaryrefslogtreecommitdiffstats
path: root/vendor/maunium.net
diff options
context:
space:
mode:
authormsglm <msglm@techchud.xyz>2023-10-27 07:08:25 -0500
committermsglm <msglm@techchud.xyz>2023-10-27 07:08:25 -0500
commit032a7e0c1188d3507b8d9a9571f2446a43cf775b (patch)
tree2bd38c01bc7761a6195e426082ce7191ebc765a1 /vendor/maunium.net
parent56e7bd01ca09ad52b0c4f48f146a20a4f1b78696 (diff)
downloadmatterbridge-msglm-032a7e0c1188d3507b8d9a9571f2446a43cf775b.tar.gz
matterbridge-msglm-032a7e0c1188d3507b8d9a9571f2446a43cf775b.tar.bz2
matterbridge-msglm-032a7e0c1188d3507b8d9a9571f2446a43cf775b.zip
apply https://github.com/42wim/matterbridge/pull/1864v1.26.0+0.1.0
Diffstat (limited to 'vendor/maunium.net')
-rw-r--r--vendor/maunium.net/go/maulogger/v2/LICENSE374
-rw-r--r--vendor/maunium.net/go/maulogger/v2/README.md6
-rw-r--r--vendor/maunium.net/go/maulogger/v2/defaults.go284
-rw-r--r--vendor/maunium.net/go/maulogger/v2/level.go47
-rw-r--r--vendor/maunium.net/go/maulogger/v2/logger.go224
-rw-r--r--vendor/maunium.net/go/maulogger/v2/maulogadapt/mauzerolog.go185
-rw-r--r--vendor/maunium.net/go/maulogger/v2/maulogadapt/zeromaulog.go73
-rw-r--r--vendor/maunium.net/go/maulogger/v2/sublogger.go216
-rw-r--r--vendor/maunium.net/go/maulogger/v2/writer.go78
-rw-r--r--vendor/maunium.net/go/mautrix/.editorconfig12
-rw-r--r--vendor/maunium.net/go/mautrix/.gitignore4
-rw-r--r--vendor/maunium.net/go/mautrix/.pre-commit-config.yaml15
-rw-r--r--vendor/maunium.net/go/mautrix/CHANGELOG.md612
-rw-r--r--vendor/maunium.net/go/mautrix/LICENSE373
-rw-r--r--vendor/maunium.net/go/mautrix/README.md24
-rw-r--r--vendor/maunium.net/go/mautrix/appservice/appservice.go350
-rw-r--r--vendor/maunium.net/go/mautrix/appservice/eventprocessor.go175
-rw-r--r--vendor/maunium.net/go/mautrix/appservice/http.go348
-rw-r--r--vendor/maunium.net/go/mautrix/appservice/intent.go419
-rw-r--r--vendor/maunium.net/go/mautrix/appservice/protocol.go152
-rw-r--r--vendor/maunium.net/go/mautrix/appservice/registration.go100
-rw-r--r--vendor/maunium.net/go/mautrix/appservice/txnid.go43
-rw-r--r--vendor/maunium.net/go/mautrix/appservice/websocket.go408
-rw-r--r--vendor/maunium.net/go/mautrix/client.go2023
-rw-r--r--vendor/maunium.net/go/mautrix/crypto/attachment/attachments.go239
-rw-r--r--vendor/maunium.net/go/mautrix/crypto/utils/utils.go133
-rw-r--r--vendor/maunium.net/go/mautrix/error.go154
-rw-r--r--vendor/maunium.net/go/mautrix/event/accountdata.go45
-rw-r--r--vendor/maunium.net/go/mautrix/event/beeper.go51
-rw-r--r--vendor/maunium.net/go/mautrix/event/content.go506
-rw-r--r--vendor/maunium.net/go/mautrix/event/encryption.go149
-rw-r--r--vendor/maunium.net/go/mautrix/event/ephemeral.go140
-rw-r--r--vendor/maunium.net/go/mautrix/event/events.go149
-rw-r--r--vendor/maunium.net/go/mautrix/event/member.go53
-rw-r--r--vendor/maunium.net/go/mautrix/event/message.go276
-rw-r--r--vendor/maunium.net/go/mautrix/event/powerlevels.go149
-rw-r--r--vendor/maunium.net/go/mautrix/event/relations.go234
-rw-r--r--vendor/maunium.net/go/mautrix/event/reply.go100
-rw-r--r--vendor/maunium.net/go/mautrix/event/state.go176
-rw-r--r--vendor/maunium.net/go/mautrix/event/type.go256
-rw-r--r--vendor/maunium.net/go/mautrix/event/verification.go307
-rw-r--r--vendor/maunium.net/go/mautrix/event/voip.go116
-rw-r--r--vendor/maunium.net/go/mautrix/filter.go93
-rw-r--r--vendor/maunium.net/go/mautrix/id/contenturi.go158
-rw-r--r--vendor/maunium.net/go/mautrix/id/crypto.go149
-rw-r--r--vendor/maunium.net/go/mautrix/id/matrixuri.go293
-rw-r--r--vendor/maunium.net/go/mautrix/id/opaque.go83
-rw-r--r--vendor/maunium.net/go/mautrix/id/trust.go87
-rw-r--r--vendor/maunium.net/go/mautrix/id/userid.go224
-rw-r--r--vendor/maunium.net/go/mautrix/pushrules/action.go124
-rw-r--r--vendor/maunium.net/go/mautrix/pushrules/condition.go266
-rw-r--r--vendor/maunium.net/go/mautrix/pushrules/doc.go2
-rw-r--r--vendor/maunium.net/go/mautrix/pushrules/glob/LICENSE22
-rw-r--r--vendor/maunium.net/go/mautrix/pushrules/glob/README.md28
-rw-r--r--vendor/maunium.net/go/mautrix/pushrules/glob/glob.go108
-rw-r--r--vendor/maunium.net/go/mautrix/pushrules/pushrules.go37
-rw-r--r--vendor/maunium.net/go/mautrix/pushrules/rule.go154
-rw-r--r--vendor/maunium.net/go/mautrix/pushrules/ruleset.go88
-rw-r--r--vendor/maunium.net/go/mautrix/requests.go437
-rw-r--r--vendor/maunium.net/go/mautrix/responses.go600
-rw-r--r--vendor/maunium.net/go/mautrix/room.go54
-rw-r--r--vendor/maunium.net/go/mautrix/statestore.go221
-rw-r--r--vendor/maunium.net/go/mautrix/sync.go310
-rw-r--r--vendor/maunium.net/go/mautrix/syncstore.go149
-rw-r--r--vendor/maunium.net/go/mautrix/url.go106
-rw-r--r--vendor/maunium.net/go/mautrix/util/base58/README.md9
-rw-r--r--vendor/maunium.net/go/mautrix/util/base58/alphabet.go49
-rw-r--r--vendor/maunium.net/go/mautrix/util/base58/base58.go138
-rw-r--r--vendor/maunium.net/go/mautrix/util/base58/base58check.go52
-rw-r--r--vendor/maunium.net/go/mautrix/util/base58/doc.go29
-rw-r--r--vendor/maunium.net/go/mautrix/util/dualerror.go33
-rw-r--r--vendor/maunium.net/go/mautrix/util/gjson.go31
-rw-r--r--vendor/maunium.net/go/mautrix/util/jsontime/jsontime.go86
-rw-r--r--vendor/maunium.net/go/mautrix/util/marshal.go30
-rw-r--r--vendor/maunium.net/go/mautrix/util/mimetypes.go50
-rw-r--r--vendor/maunium.net/go/mautrix/util/random.go65
-rw-r--r--vendor/maunium.net/go/mautrix/util/returnonce.go23
-rw-r--r--vendor/maunium.net/go/mautrix/util/ringbuffer.go77
-rw-r--r--vendor/maunium.net/go/mautrix/util/syncmap.go94
-rw-r--r--vendor/maunium.net/go/mautrix/version.go5
-rw-r--r--vendor/maunium.net/go/mautrix/versions.go153
81 files changed, 14495 insertions, 0 deletions
diff --git a/vendor/maunium.net/go/maulogger/v2/LICENSE b/vendor/maunium.net/go/maulogger/v2/LICENSE
new file mode 100644
index 00000000..52d13511
--- /dev/null
+++ b/vendor/maunium.net/go/maulogger/v2/LICENSE
@@ -0,0 +1,374 @@
+Mozilla Public License Version 2.0
+==================================
+
+1. Definitions
+--------------
+
+1.1. "Contributor"
+ means each individual or legal entity that creates, contributes to
+ the creation of, or owns Covered Software.
+
+1.2. "Contributor Version"
+ means the combination of the Contributions of others (if any) used
+ by a Contributor and that particular Contributor's Contribution.
+
+1.3. "Contribution"
+ means Covered Software of a particular Contributor.
+
+1.4. "Covered Software"
+ means Source Code Form to which the initial Contributor has attached
+ the notice in Exhibit A, the Executable Form of such Source Code
+ Form, and Modifications of such Source Code Form, in each case
+ including portions thereof.
+
+1.5. "Incompatible With Secondary Licenses"
+ means
+
+ (a) that the initial Contributor has attached the notice described
+ in Exhibit B to the Covered Software; or
+
+ (b) that the Covered Software was made available under the terms of
+ version 1.1 or earlier of the License, but not also under the
+ terms of a Secondary License.
+
+1.6. "Executable Form"
+ means any form of the work other than Source Code Form.
+
+1.7. "Larger Work"
+ means a work that combines Covered Software with other material, in
+ a separate file or files, that is not Covered Software.
+
+1.8. "License"
+ means this document.
+
+1.9. "Licensable"
+ means having the right to grant, to the maximum extent possible,
+ whether at the time of the initial grant or subsequently, any and
+ all of the rights conveyed by this License.
+
+1.10. "Modifications"
+ means any of the following:
+
+ (a) any file in Source Code Form that results from an addition to,
+ deletion from, or modification of the contents of Covered
+ Software; or
+
+ (b) any new file in Source Code Form that contains any Covered
+ Software.
+
+1.11. "Patent Claims" of a Contributor
+ means any patent claim(s), including without limitation, method,
+ process, and apparatus claims, in any patent Licensable by such
+ Contributor that would be infringed, but for the grant of the
+ License, by the making, using, selling, offering for sale, having
+ made, import, or transfer of either its Contributions or its
+ Contributor Version.
+
+1.12. "Secondary License"
+ means either the GNU General Public License, Version 2.0, the GNU
+ Lesser General Public License, Version 2.1, the GNU Affero General
+ Public License, Version 3.0, or any later versions of those
+ licenses.
+
+1.13. "Source Code Form"
+ means the form of the work preferred for making modifications.
+
+1.14. "You" (or "Your")
+ means an individual or a legal entity exercising rights under this
+ License. For legal entities, "You" includes any entity that
+ controls, is controlled by, or is under common control with You. For
+ purposes of this definition, "control" means (a) the power, direct
+ or indirect, to cause the direction or management of such entity,
+ whether by contract or otherwise, or (b) ownership of more than
+ fifty percent (50%) of the outstanding shares or beneficial
+ ownership of such entity.
+
+2. License Grants and Conditions
+--------------------------------
+
+2.1. Grants
+
+Each Contributor hereby grants You a world-wide, royalty-free,
+non-exclusive license:
+
+(a) under intellectual property rights (other than patent or trademark)
+ Licensable by such Contributor to use, reproduce, make available,
+ modify, display, perform, distribute, and otherwise exploit its
+ Contributions, either on an unmodified basis, with Modifications, or
+ as part of a Larger Work; and
+
+(b) under Patent Claims of such Contributor to make, use, sell, offer
+ for sale, have made, import, and otherwise transfer either its
+ Contributions or its Contributor Version.
+
+2.2. Effective Date
+
+The licenses granted in Section 2.1 with respect to any Contribution
+become effective for each Contribution on the date the Contributor first
+distributes such Contribution.
+
+2.3. Limitations on Grant Scope
+
+The licenses granted in this Section 2 are the only rights granted under
+this License. No additional rights or licenses will be implied from the
+distribution or licensing of Covered Software under this License.
+Notwithstanding Section 2.1(b) above, no patent license is granted by a
+Contributor:
+
+(a) for any code that a Contributor has removed from Covered Software;
+ or
+
+(b) for infringements caused by: (i) Your and any other third party's
+ modifications of Covered Software, or (ii) the combination of its
+ Contributions with other software (except as part of its Contributor
+ Version); or
+
+(c) under Patent Claims infringed by Covered Software in the absence of
+ its Contributions.
+
+This License does not grant any rights in the trademarks, service marks,
+or logos of any Contributor (except as may be necessary to comply with
+the notice requirements in Section 3.4).
+
+2.4. Subsequent Licenses
+
+No Contributor makes additional grants as a result of Your choice to
+distribute the Covered Software under a subsequent version of this
+License (see Section 10.2) or under the terms of a Secondary License (if
+permitted under the terms of Section 3.3).
+
+2.5. Representation
+
+Each Contributor represents that the Contributor believes its
+Contributions are its original creation(s) or it has sufficient rights
+to grant the rights to its Contributions conveyed by this License.
+
+2.6. Fair Use
+
+This License is not intended to limit any rights You have under
+applicable copyright doctrines of fair use, fair dealing, or other
+equivalents.
+
+2.7. Conditions
+
+Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
+in Section 2.1.
+
+3. Responsibilities
+-------------------
+
+3.1. Distribution of Source Form
+
+All distribution of Covered Software in Source Code Form, including any
+Modifications that You create or to which You contribute, must be under
+the terms of this License. You must inform recipients that the Source
+Code Form of the Covered Software is governed by the terms of this
+License, and how they can obtain a copy of this License. You may not
+attempt to alter or restrict the recipients' rights in the Source Code
+Form.
+
+3.2. Distribution of Executable Form
+
+If You distribute Covered Software in Executable Form then:
+
+(a) such Covered Software must also be made available in Source Code
+ Form, as described in Section 3.1, and You must inform recipients of
+ the Executable Form how they can obtain a copy of such Source Code
+ Form by reasonable means in a timely manner, at a charge no more
+ than the cost of distribution to the recipient; and
+
+(b) You may distribute such Executable Form under the terms of this
+ License, or sublicense it under different terms, provided that the
+ license for the Executable Form does not attempt to limit or alter
+ the recipients' rights in the Source Code Form under this License.
+
+3.3. Distribution of a Larger Work
+
+You may create and distribute a Larger Work under terms of Your choice,
+provided that You also comply with the requirements of this License for
+the Covered Software. If the Larger Work is a combination of Covered
+Software with a work governed by one or more Secondary Licenses, and the
+Covered Software is not Incompatible With Secondary Licenses, this
+License permits You to additionally distribute such Covered Software
+under the terms of such Secondary License(s), so that the recipient of
+the Larger Work may, at their option, further distribute the Covered
+Software under the terms of either this License or such Secondary
+License(s).
+
+3.4. Notices
+
+You may not remove or alter the substance of any license notices
+(including copyright notices, patent notices, disclaimers of warranty,
+or limitations of liability) contained within the Source Code Form of
+the Covered Software, except that You may alter any license notices to
+the extent required to remedy known factual inaccuracies.
+
+3.5. Application of Additional Terms
+
+You may choose to offer, and to charge a fee for, warranty, support,
+indemnity or liability obligations to one or more recipients of Covered
+Software. However, You may do so only on Your own behalf, and not on
+behalf of any Contributor. You must make it absolutely clear that any
+such warranty, support, indemnity, or liability obligation is offered by
+You alone, and You hereby agree to indemnify every Contributor for any
+liability incurred by such Contributor as a result of warranty, support,
+indemnity or liability terms You offer. You may include additional
+disclaimers of warranty and limitations of liability specific to any
+jurisdiction.
+
+4. Inability to Comply Due to Statute or Regulation
+---------------------------------------------------
+
+If it is impossible for You to comply with any of the terms of this
+License with respect to some or all of the Covered Software due to
+statute, judicial order, or regulation then You must: (a) comply with
+the terms of this License to the maximum extent possible; and (b)
+describe the limitations and the code they affect. Such description must
+be placed in a text file included with all distributions of the Covered
+Software under this License. Except to the extent prohibited by statute
+or regulation, such description must be sufficiently detailed for a
+recipient of ordinary skill to be able to understand it.
+
+5. Termination
+--------------
+
+5.1. The rights granted under this License will terminate automatically
+if You fail to comply with any of its terms. However, if You become
+compliant, then the rights granted under this License from a particular
+Contributor are reinstated (a) provisionally, unless and until such
+Contributor explicitly and finally terminates Your grants, and (b) on an
+ongoing basis, if such Contributor fails to notify You of the
+non-compliance by some reasonable means prior to 60 days after You have
+come back into compliance. Moreover, Your grants from a particular
+Contributor are reinstated on an ongoing basis if such Contributor
+notifies You of the non-compliance by some reasonable means, this is the
+first time You have received notice of non-compliance with this License
+from such Contributor, and You become compliant prior to 30 days after
+Your receipt of the notice.
+
+5.2. If You initiate litigation against any entity by asserting a patent
+infringement claim (excluding declaratory judgment actions,
+counter-claims, and cross-claims) alleging that a Contributor Version
+directly or indirectly infringes any patent, then the rights granted to
+You by any and all Contributors for the Covered Software under Section
+2.1 of this License shall terminate.
+
+5.3. In the event of termination under Sections 5.1 or 5.2 above, all
+end user license agreements (excluding distributors and resellers) which
+have been validly granted by You or Your distributors under this License
+prior to termination shall survive termination.
+
+************************************************************************
+* *
+* 6. Disclaimer of Warranty *
+* ------------------------- *
+* *
+* Covered Software is provided under this License on an "as is" *
+* basis, without warranty of any kind, either expressed, implied, or *
+* statutory, including, without limitation, warranties that the *
+* Covered Software is free of defects, merchantable, fit for a *
+* particular purpose or non-infringing. The entire risk as to the *
+* quality and performance of the Covered Software is with You. *
+* Should any Covered Software prove defective in any respect, You *
+* (not any Contributor) assume the cost of any necessary servicing, *
+* repair, or correction. This disclaimer of warranty constitutes an *
+* essential part of this License. No use of any Covered Software is *
+* authorized under this License except under this disclaimer. *
+* *
+************************************************************************
+
+************************************************************************
+* *
+* 7. Limitation of Liability *
+* -------------------------- *
+* *
+* Under no circumstances and under no legal theory, whether tort *
+* (including negligence), contract, or otherwise, shall any *
+* Contributor, or anyone who distributes Covered Software as *
+* permitted above, be liable to You for any direct, indirect, *
+* special, incidental, or consequential damages of any character *
+* including, without limitation, damages for lost profits, loss of *
+* goodwill, work stoppage, computer failure or malfunction, or any *
+* and all other commercial damages or losses, even if such party *
+* shall have been informed of the possibility of such damages. This *
+* limitation of liability shall not apply to liability for death or *
+* personal injury resulting from such party's negligence to the *
+* extent applicable law prohibits such limitation. Some *
+* jurisdictions do not allow the exclusion or limitation of *
+* incidental or consequential damages, so this exclusion and *
+* limitation may not apply to You. *
+* *
+************************************************************************
+
+8. Litigation
+-------------
+
+Any litigation relating to this License may be brought only in the
+courts of a jurisdiction where the defendant maintains its principal
+place of business and such litigation shall be governed by laws of that
+jurisdiction, without reference to its conflict-of-law provisions.
+Nothing in this Section shall prevent a party's ability to bring
+cross-claims or counter-claims.
+
+9. Miscellaneous
+----------------
+
+This License represents the complete agreement concerning the subject
+matter hereof. If any provision of this License is held to be
+unenforceable, such provision shall be reformed only to the extent
+necessary to make it enforceable. Any law or regulation which provides
+that the language of a contract shall be construed against the drafter
+shall not be used to construe this License against a Contributor.
+
+10. Versions of the License
+---------------------------
+
+10.1. New Versions
+
+Mozilla Foundation is the license steward. Except as provided in Section
+10.3, no one other than the license steward has the right to modify or
+publish new versions of this License. Each version will be given a
+distinguishing version number.
+
+10.2. Effect of New Versions
+
+You may distribute the Covered Software under the terms of the version
+of the License under which You originally received the Covered Software,
+or under the terms of any subsequent version published by the license
+steward.
+
+10.3. Modified Versions
+
+If you create software not governed by this License, and you want to
+create a new license for such software, you may create and use a
+modified version of this License if you rename the license and remove
+any references to the name of the license steward (except to note that
+such modified license differs from this License).
+
+10.4. Distributing Source Code Form that is Incompatible With Secondary
+Licenses
+
+If You choose to distribute Source Code Form that is Incompatible With
+Secondary Licenses under the terms of this version of the License, the
+notice described in Exhibit B of this License must be attached.
+
+Exhibit A - Source Code Form License Notice
+-------------------------------------------
+
+ This Source Code Form is subject to the terms of the Mozilla Public
+ License, v. 2.0. If a copy of the MPL was not distributed with this
+ file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+If it is not possible or desirable to put the notice in a particular
+file, then You may include the notice in a location (such as a LICENSE
+file in a relevant directory) where a recipient would be likely to look
+for such a notice.
+
+You may add additional accurate notices of copyright ownership.
+
+Exhibit B - "Incompatible With Secondary Licenses" Notice
+---------------------------------------------------------
+
+ This Source Code Form is "Incompatible With Secondary Licenses", as
+ defined by the Mozilla Public License, v. 2.0.
+
diff --git a/vendor/maunium.net/go/maulogger/v2/README.md b/vendor/maunium.net/go/maulogger/v2/README.md
new file mode 100644
index 00000000..97db2f58
--- /dev/null
+++ b/vendor/maunium.net/go/maulogger/v2/README.md
@@ -0,0 +1,6 @@
+# maulogger
+A logger in Go. Deprecated in favor of [zerolog](https://github.com/rs/zerolog).
+
+Utilities for migrating gracefully can be found in the maulogadapt package,
+it includes both wrapping a zerolog in the maulogger interface, and wrapping a
+maulogger as a zerolog output writer.
diff --git a/vendor/maunium.net/go/maulogger/v2/defaults.go b/vendor/maunium.net/go/maulogger/v2/defaults.go
new file mode 100644
index 00000000..571e79cc
--- /dev/null
+++ b/vendor/maunium.net/go/maulogger/v2/defaults.go
@@ -0,0 +1,284 @@
+// mauLogger - A logger for Go programs
+// Copyright (c) 2016-2021 Tulir Asokan
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package maulogger
+
+import (
+ "os"
+)
+
+// DefaultLogger ...
+var DefaultLogger = Create().(*BasicLogger)
+
+// SetWriter formats the given parts with fmt.Sprint and logs the result with the SetWriter level
+func SetWriter(w *os.File) {
+ DefaultLogger.SetWriter(w)
+}
+
+// OpenFile formats the given parts with fmt.Sprint and logs the result with the OpenFile level
+func OpenFile() error {
+ return DefaultLogger.OpenFile()
+}
+
+// Close formats the given parts with fmt.Sprint and logs the result with the Close level
+func Close() error {
+ return DefaultLogger.Close()
+}
+
+// Sub creates a Sublogger
+func Sub(module string) Logger {
+ return DefaultLogger.Sub(module)
+}
+
+// Raw formats the given parts with fmt.Sprint and logs the result with the Raw level
+func Rawm(level Level, metadata map[string]interface{}, module, message string) {
+ DefaultLogger.Raw(level, metadata, module, message)
+}
+
+func Raw(level Level, module, message string) {
+ DefaultLogger.Raw(level, map[string]interface{}{}, module, message)
+}
+
+// Log formats the given parts with fmt.Sprint and logs the result with the given level
+func Log(level Level, parts ...interface{}) {
+ DefaultLogger.DefaultSub.Log(level, parts...)
+}
+
+// Logln formats the given parts with fmt.Sprintln and logs the result with the given level
+func Logln(level Level, parts ...interface{}) {
+ DefaultLogger.DefaultSub.Logln(level, parts...)
+}
+
+// Logf formats the given message and args with fmt.Sprintf and logs the result with the given level
+func Logf(level Level, message string, args ...interface{}) {
+ DefaultLogger.DefaultSub.Logf(level, message, args...)
+}
+
+// Logfln formats the given message and args with fmt.Sprintf, appends a newline and logs the result with the given level
+func Logfln(level Level, message string, args ...interface{}) {
+ DefaultLogger.DefaultSub.Logfln(level, message, args...)
+}
+
+// Debug formats the given parts with fmt.Sprint and logs the result with the Debug level
+func Debug(parts ...interface{}) {
+ DefaultLogger.DefaultSub.Debug(parts...)
+}
+
+// Debugln formats the given parts with fmt.Sprintln and logs the result with the Debug level
+func Debugln(parts ...interface{}) {
+ DefaultLogger.DefaultSub.Debugln(parts...)
+}
+
+// Debugf formats the given message and args with fmt.Sprintf and logs the result with the Debug level
+func Debugf(message string, args ...interface{}) {
+ DefaultLogger.DefaultSub.Debugf(message, args...)
+}
+
+// Debugfln formats the given message and args with fmt.Sprintf, appends a newline and logs the result with the Debug level
+func Debugfln(message string, args ...interface{}) {
+ DefaultLogger.DefaultSub.Debugfln(message, args...)
+}
+
+// Info formats the given parts with fmt.Sprint and logs the result with the Info level
+func Info(parts ...interface{}) {
+ DefaultLogger.DefaultSub.Info(parts...)
+}
+
+// Infoln formats the given parts with fmt.Sprintln and logs the result with the Info level
+func Infoln(parts ...interface{}) {
+ DefaultLogger.DefaultSub.Infoln(parts...)
+}
+
+// Infof formats the given message and args with fmt.Sprintf and logs the result with the Info level
+func Infof(message string, args ...interface{}) {
+ DefaultLogger.DefaultSub.Infof(message, args...)
+}
+
+// Infofln formats the given message and args with fmt.Sprintf, appends a newline and logs the result with the Info level
+func Infofln(message string, args ...interface{}) {
+ DefaultLogger.DefaultSub.Infofln(message, args...)
+}
+
+// Warn formats the given parts with fmt.Sprint and logs the result with the Warn level
+func Warn(parts ...interface{}) {
+ DefaultLogger.DefaultSub.Warn(parts...)
+}
+
+// Warnln formats the given parts with fmt.Sprintln and logs the result with the Warn level
+func Warnln(parts ...interface{}) {
+ DefaultLogger.DefaultSub.Warnln(parts...)
+}
+
+// Warnf formats the given message and args with fmt.Sprintf and logs the result with the Warn level
+func Warnf(message string, args ...interface{}) {
+ DefaultLogger.DefaultSub.Warnf(message, args...)
+}
+
+// Warnfln formats the given message and args with fmt.Sprintf, appends a newline and logs the result with the Warn level
+func Warnfln(message string, args ...interface{}) {
+ DefaultLogger.DefaultSub.Warnfln(message, args...)
+}
+
+// Error formats the given parts with fmt.Sprint and logs the result with the Error level
+func Error(parts ...interface{}) {
+ DefaultLogger.DefaultSub.Error(parts...)
+}
+
+// Errorln formats the given parts with fmt.Sprintln and logs the result with the Error level
+func Errorln(parts ...interface{}) {
+ DefaultLogger.DefaultSub.Errorln(parts...)
+}
+
+// Errorf formats the given message and args with fmt.Sprintf and logs the result with the Error level
+func Errorf(message string, args ...interface{}) {
+ DefaultLogger.DefaultSub.Errorf(message, args...)
+}
+
+// Errorfln formats the given message and args with fmt.Sprintf, appends a newline and logs the result with the Error level
+func Errorfln(message string, args ...interface{}) {
+ DefaultLogger.DefaultSub.Errorfln(message, args...)
+}
+
+// Fatal formats the given parts with fmt.Sprint and logs the result with the Fatal level
+func Fatal(parts ...interface{}) {
+ DefaultLogger.DefaultSub.Fatal(parts...)
+}
+
+// Fatalln formats the given parts with fmt.Sprintln and logs the result with the Fatal level
+func Fatalln(parts ...interface{}) {
+ DefaultLogger.DefaultSub.Fatalln(parts...)
+}
+
+// Fatalf formats the given message and args with fmt.Sprintf and logs the result with the Fatal level
+func Fatalf(message string, args ...interface{}) {
+ DefaultLogger.DefaultSub.Fatalf(message, args...)
+}
+
+// Fatalfln formats the given message and args with fmt.Sprintf, appends a newline and logs the result with the Fatal level
+func Fatalfln(message string, args ...interface{}) {
+ DefaultLogger.DefaultSub.Fatalfln(message, args...)
+}
+
+// Log formats the given parts with fmt.Sprint and logs the result with the given level
+func (log *BasicLogger) Log(level Level, parts ...interface{}) {
+ log.DefaultSub.Log(level, parts...)
+}
+
+// Logln formats the given parts with fmt.Sprintln and logs the result with the given level
+func (log *BasicLogger) Logln(level Level, parts ...interface{}) {
+ log.DefaultSub.Logln(level, parts...)
+}
+
+// Logf formats the given message and args with fmt.Sprintf and logs the result with the given level
+func (log *BasicLogger) Logf(level Level, message string, args ...interface{}) {
+ log.DefaultSub.Logf(level, message, args...)
+}
+
+// Logfln formats the given message and args with fmt.Sprintf, appends a newline and logs the result with the given level
+func (log *BasicLogger) Logfln(level Level, message string, args ...interface{}) {
+ log.DefaultSub.Logfln(level, message, args...)
+}
+
+// Debug formats the given parts with fmt.Sprint and logs the result with the Debug level
+func (log *BasicLogger) Debug(parts ...interface{}) {
+ log.DefaultSub.Debug(parts...)
+}
+
+// Debugln formats the given parts with fmt.Sprintln and logs the result with the Debug level
+func (log *BasicLogger) Debugln(parts ...interface{}) {
+ log.DefaultSub.Debugln(parts...)
+}
+
+// Debugf formats the given message and args with fmt.Sprintf and logs the result with the Debug level
+func (log *BasicLogger) Debugf(message string, args ...interface{}) {
+ log.DefaultSub.Debugf(message, args...)
+}
+
+// Debugfln formats the given message and args with fmt.Sprintf, appends a newline and logs the result with the Debug level
+func (log *BasicLogger) Debugfln(message string, args ...interface{}) {
+ log.DefaultSub.Debugfln(message, args...)
+}
+
+// Info formats the given parts with fmt.Sprint and logs the result with the Info level
+func (log *BasicLogger) Info(parts ...interface{}) {
+ log.DefaultSub.Info(parts...)
+}
+
+// Infoln formats the given parts with fmt.Sprintln and logs the result with the Info level
+func (log *BasicLogger) Infoln(parts ...interface{}) {
+ log.DefaultSub.Infoln(parts...)
+}
+
+// Infofln formats the given message and args with fmt.Sprintf, appends a newline and logs the result with the Info level
+func (log *BasicLogger) Infofln(message string, args ...interface{}) {
+ log.DefaultSub.Infofln(message, args...)
+}
+
+// Infof formats the given message and args with fmt.Sprintf and logs the result with the Info level
+func (log *BasicLogger) Infof(message string, args ...interface{}) {
+ log.DefaultSub.Infof(message, args...)
+}
+
+// Warn formats the given parts with fmt.Sprint and logs the result with the Warn level
+func (log *BasicLogger) Warn(parts ...interface{}) {
+ log.DefaultSub.Warn(parts...)
+}
+
+// Warnln formats the given parts with fmt.Sprintln and logs the result with the Warn level
+func (log *BasicLogger) Warnln(parts ...interface{}) {
+ log.DefaultSub.Warnln(parts...)
+}
+
+// Warnfln formats the given message and args with fmt.Sprintf, appends a newline and logs the result with the Warn level
+func (log *BasicLogger) Warnfln(message string, args ...interface{}) {
+ log.DefaultSub.Warnfln(message, args...)
+}
+
+// Warnf formats the given message and args with fmt.Sprintf and logs the result with the Warn level
+func (log *BasicLogger) Warnf(message string, args ...interface{}) {
+ log.DefaultSub.Warnf(message, args...)
+}
+
+// Error formats the given parts with fmt.Sprint and logs the result with the Error level
+func (log *BasicLogger) Error(parts ...interface{}) {
+ log.DefaultSub.Error(parts...)
+}
+
+// Errorln formats the given parts with fmt.Sprintln and logs the result with the Error level
+func (log *BasicLogger) Errorln(parts ...interface{}) {
+ log.DefaultSub.Errorln(parts...)
+}
+
+// Errorf formats the given message and args with fmt.Sprintf and logs the result with the Error level
+func (log *BasicLogger) Errorf(message string, args ...interface{}) {
+ log.DefaultSub.Errorf(message, args...)
+}
+
+// Errorfln formats the given message and args with fmt.Sprintf, appends a newline and logs the result with the Error level
+func (log *BasicLogger) Errorfln(message string, args ...interface{}) {
+ log.DefaultSub.Errorfln(message, args...)
+}
+
+// Fatal formats the given parts with fmt.Sprint and logs the result with the Fatal level
+func (log *BasicLogger) Fatal(parts ...interface{}) {
+ log.DefaultSub.Fatal(parts...)
+}
+
+// Fatalln formats the given parts with fmt.Sprintln and logs the result with the Fatal level
+func (log *BasicLogger) Fatalln(parts ...interface{}) {
+ log.DefaultSub.Fatalln(parts...)
+}
+
+// Fatalf formats the given message and args with fmt.Sprintf and logs the result with the Fatal level
+func (log *BasicLogger) Fatalf(message string, args ...interface{}) {
+ log.DefaultSub.Fatalf(message, args...)
+}
+
+// Fatalfln formats the given message and args with fmt.Sprintf, appends a newline and logs the result with the Fatal level
+func (log *BasicLogger) Fatalfln(message string, args ...interface{}) {
+ log.DefaultSub.Fatalfln(message, args...)
+}
diff --git a/vendor/maunium.net/go/maulogger/v2/level.go b/vendor/maunium.net/go/maulogger/v2/level.go
new file mode 100644
index 00000000..392dccd8
--- /dev/null
+++ b/vendor/maunium.net/go/maulogger/v2/level.go
@@ -0,0 +1,47 @@
+// mauLogger - A logger for Go programs
+// Copyright (c) 2016-2021 Tulir Asokan
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package maulogger
+
+import (
+ "fmt"
+)
+
+// Level is the severity level of a log entry.
+type Level struct {
+ Name string
+ Severity, Color int
+}
+
+var (
+ // LevelDebug is the level for debug messages.
+ LevelDebug = Level{Name: "DEBUG", Color: -1, Severity: 0}
+ // LevelInfo is the level for basic log messages.
+ LevelInfo = Level{Name: "INFO", Color: 36, Severity: 10}
+ // LevelWarn is the level saying that something went wrong, but the program will continue operating mostly normally.
+ LevelWarn = Level{Name: "WARN", Color: 33, Severity: 50}
+ // LevelError is the level saying that something went wrong and the program may not operate as expected, but will still continue.
+ LevelError = Level{Name: "ERROR", Color: 31, Severity: 100}
+ // LevelFatal is the level saying that something went wrong and the program will not operate normally.
+ LevelFatal = Level{Name: "FATAL", Color: 35, Severity: 9001}
+)
+
+// GetColor gets the ANSI escape color code for the log level.
+func (lvl Level) GetColor() string {
+ if lvl.Color < 0 {
+ return "\x1b[0m"
+ }
+ return fmt.Sprintf("\x1b[%dm", lvl.Color)
+}
+
+// GetReset gets the ANSI escape reset code.
+func (lvl Level) GetReset() string {
+ if lvl.Color < 0 {
+ return ""
+ }
+ return "\x1b[0m"
+}
diff --git a/vendor/maunium.net/go/maulogger/v2/logger.go b/vendor/maunium.net/go/maulogger/v2/logger.go
new file mode 100644
index 00000000..61cd1e1d
--- /dev/null
+++ b/vendor/maunium.net/go/maulogger/v2/logger.go
@@ -0,0 +1,224 @@
+// mauLogger - A logger for Go programs
+// Copyright (c) 2016-2021 Tulir Asokan
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package maulogger
+
+import (
+ "encoding/json"
+ "fmt"
+ "io"
+ "os"
+ "strings"
+ "sync"
+ "time"
+)
+
+// LoggerFileFormat ...
+type LoggerFileFormat func(now string, i int) string
+
+type BasicLogger struct {
+ PrintLevel int
+ FlushLineThreshold int
+ FileTimeFormat string
+ FileFormat LoggerFileFormat
+ TimeFormat string
+ FileMode os.FileMode
+ DefaultSub Logger
+
+ JSONFile bool
+ JSONStdout bool
+
+ stdoutEncoder *json.Encoder
+ fileEncoder *json.Encoder
+
+ writer *os.File
+ writerLock sync.Mutex
+ StdoutLock sync.Mutex
+ StderrLock sync.Mutex
+ lines int
+
+ metadata map[string]interface{}
+}
+
+// Logger contains advanced logging functions
+type Logger interface {
+ Sub(module string) Logger
+ Subm(module string, metadata map[string]interface{}) Logger
+ WithDefaultLevel(level Level) Logger
+ GetParent() Logger
+
+ Writer(level Level) io.WriteCloser
+
+ Log(level Level, parts ...interface{})
+ Logln(level Level, parts ...interface{})
+ Logf(level Level, message string, args ...interface{})
+ Logfln(level Level, message string, args ...interface{})
+
+ Debug(parts ...interface{})
+ Debugln(parts ...interface{})
+ Debugf(message string, args ...interface{})
+ Debugfln(message string, args ...interface{})
+ Info(parts ...interface{})
+ Infoln(parts ...interface{})
+ Infof(message string, args ...interface{})
+ Infofln(message string, args ...interface{})
+ Warn(parts ...interface{})
+ Warnln(parts ...interface{})
+ Warnf(message string, args ...interface{})
+ Warnfln(message string, args ...interface{})
+ Error(parts ...interface{})
+ Errorln(parts ...interface{})
+ Errorf(message string, args ...interface{})
+ Errorfln(message string, args ...interface{})
+ Fatal(parts ...interface{})
+ Fatalln(parts ...interface{})
+ Fatalf(message string, args ...interface{})
+ Fatalfln(message string, args ...interface{})
+}
+
+// Create a Logger
+func Createm(metadata map[string]interface{}) Logger {
+ var log = &BasicLogger{
+ PrintLevel: 10,
+ FileTimeFormat: "2006-01-02",
+ FileFormat: func(now string, i int) string { return fmt.Sprintf("%[1]s-%02[2]d.log", now, i) },
+ TimeFormat: "15:04:05 02.01.2006",
+ FileMode: 0600,
+ FlushLineThreshold: 5,
+ lines: 0,
+ metadata: metadata,
+ }
+ log.DefaultSub = log.Sub("")
+ return log
+}
+
+func Create() Logger {
+ return Createm(map[string]interface{}{})
+}
+
+func (log *BasicLogger) EnableJSONStdout() {
+ log.JSONStdout = true
+ log.stdoutEncoder = json.NewEncoder(os.Stdout)
+}
+
+func (log *BasicLogger) GetParent() Logger {
+ return nil
+}
+
+// SetWriter formats the given parts with fmt.Sprint and logs the result with the SetWriter level
+func (log *BasicLogger) SetWriter(w *os.File) {
+ log.writer = w
+ if log.JSONFile {
+ log.fileEncoder = json.NewEncoder(w)
+ }
+}
+
+// OpenFile formats the given parts with fmt.Sprint and logs the result with the OpenFile level
+func (log *BasicLogger) OpenFile() error {
+ now := time.Now().Format(log.FileTimeFormat)
+ i := 1
+ for ; ; i++ {
+ if _, err := os.Stat(log.FileFormat(now, i)); os.IsNotExist(err) {
+ break
+ }
+ }
+ writer, err := os.OpenFile(log.FileFormat(now, i), os.O_WRONLY|os.O_CREATE|os.O_APPEND, log.FileMode)
+ if err != nil {
+ return err
+ } else if writer == nil {
+ return os.ErrInvalid
+ }
+ log.SetWriter(writer)
+ return nil
+}
+
+// Close formats the given parts with fmt.Sprint and logs the result with the Close level
+func (log *BasicLogger) Close() error {
+ if log.writer != nil {
+ return log.writer.Close()
+ }
+ return nil
+}
+
+type logLine struct {
+ log *BasicLogger
+
+ Command string `json:"command"`
+ Time time.Time `json:"time"`
+ Level string `json:"level"`
+ Module string `json:"module"`
+ Message string `json:"message"`
+ Metadata map[string]interface{} `json:"metadata"`
+}
+
+func (ll logLine) String() string {
+ if len(ll.Module) == 0 {
+ return fmt.Sprintf("[%s] [%s] %s", ll.Time.Format(ll.log.TimeFormat), ll.Level, ll.Message)
+ } else {
+ return fmt.Sprintf("[%s] [%s/%s] %s", ll.Time.Format(ll.log.TimeFormat), ll.Module, ll.Level, ll.Message)
+ }
+}
+
+func reduceItem(m1, m2 map[string]interface{}) map[string]interface{} {
+ m3 := map[string]interface{}{}
+
+ _merge := func(m map[string]interface{}) {
+ for ia, va := range m {
+ m3[ia] = va
+ }
+ }
+
+ _merge(m1)
+ _merge(m2)
+
+ return m3
+}
+
+// Raw formats the given parts with fmt.Sprint and logs the result with the Raw level
+func (log *BasicLogger) Raw(level Level, extraMetadata map[string]interface{}, module, origMessage string) {
+ message := logLine{log, "log", time.Now(), level.Name, module, strings.TrimSpace(origMessage), reduceItem(log.metadata, extraMetadata)}
+
+ if log.writer != nil {
+ log.writerLock.Lock()
+ var err error
+ if log.JSONFile {
+ err = log.fileEncoder.Encode(&message)
+ } else {
+ _, err = log.writer.WriteString(message.String())
+ _, _ = log.writer.WriteString("\n")
+ }
+ log.writerLock.Unlock()
+ if err != nil {
+ log.StderrLock.Lock()
+ _, _ = os.Stderr.WriteString("Failed to write to log file:")
+ _, _ = os.Stderr.WriteString(err.Error())
+ log.StderrLock.Unlock()
+ }
+ }
+
+ if level.Severity >= log.PrintLevel {
+ if log.JSONStdout {
+ log.StdoutLock.Lock()
+ _ = log.stdoutEncoder.Encode(&message)
+ log.StdoutLock.Unlock()
+ } else if level.Severity >= LevelError.Severity {
+ log.StderrLock.Lock()
+ _, _ = os.Stderr.WriteString(level.GetColor())
+ _, _ = os.Stderr.WriteString(message.String())
+ _, _ = os.Stderr.WriteString(level.GetReset())
+ _, _ = os.Stderr.WriteString("\n")
+ log.StderrLock.Unlock()
+ } else {
+ log.StdoutLock.Lock()
+ _, _ = os.Stdout.WriteString(level.GetColor())
+ _, _ = os.Stdout.WriteString(message.String())
+ _, _ = os.Stdout.WriteString(level.GetReset())
+ _, _ = os.Stdout.WriteString("\n")
+ log.StdoutLock.Unlock()
+ }
+ }
+}
diff --git a/vendor/maunium.net/go/maulogger/v2/maulogadapt/mauzerolog.go b/vendor/maunium.net/go/maulogger/v2/maulogadapt/mauzerolog.go
new file mode 100644
index 00000000..774c189e
--- /dev/null
+++ b/vendor/maunium.net/go/maulogger/v2/maulogadapt/mauzerolog.go
@@ -0,0 +1,185 @@
+// Copyright (c) 2023 Tulir Asokan
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package maulogadapt
+
+import (
+ "fmt"
+ "io"
+ "strings"
+
+ "github.com/rs/zerolog"
+
+ "maunium.net/go/maulogger/v2"
+)
+
+type MauZeroLog struct {
+ *zerolog.Logger
+ orig *zerolog.Logger
+ mod string
+}
+
+func ZeroAsMau(log *zerolog.Logger) maulogger.Logger {
+ return MauZeroLog{log, log, ""}
+}
+
+var _ maulogger.Logger = (*MauZeroLog)(nil)
+
+func (m MauZeroLog) Sub(module string) maulogger.Logger {
+ return m.Subm(module, map[string]interface{}{})
+}
+
+func (m MauZeroLog) Subm(module string, metadata map[string]interface{}) maulogger.Logger {
+ if m.mod != "" {
+ module = fmt.Sprintf("%s/%s", m.mod, module)
+ }
+ var orig zerolog.Logger
+ if m.orig != nil {
+ orig = *m.orig
+ } else {
+ orig = *m.Logger
+ }
+ if len(metadata) > 0 {
+ with := m.orig.With()
+ for key, value := range metadata {
+ with = with.Interface(key, value)
+ }
+ orig = with.Logger()
+ }
+ log := orig.With().Str("module", module).Logger()
+ return MauZeroLog{&log, &orig, module}
+}
+
+func (m MauZeroLog) WithDefaultLevel(_ maulogger.Level) maulogger.Logger {
+ return m
+}
+
+func (m MauZeroLog) GetParent() maulogger.Logger {
+ return nil
+}
+
+type nopWriteCloser struct {
+ io.Writer
+}
+
+func (nopWriteCloser) Close() error { return nil }
+
+func (m MauZeroLog) Writer(level maulogger.Level) io.WriteCloser {
+ return nopWriteCloser{m.Logger.With().Str(zerolog.LevelFieldName, zerolog.LevelFieldMarshalFunc(mauToZeroLevel(level))).Logger()}
+}
+
+func mauToZeroLevel(level maulogger.Level) zerolog.Level {
+ switch level {
+ case maulogger.LevelDebug:
+ return zerolog.DebugLevel
+ case maulogger.LevelInfo:
+ return zerolog.InfoLevel
+ case maulogger.LevelWarn:
+ return zerolog.WarnLevel
+ case maulogger.LevelError:
+ return zerolog.ErrorLevel
+ case maulogger.LevelFatal:
+ return zerolog.FatalLevel
+ default:
+ return zerolog.TraceLevel
+ }
+}
+
+func (m MauZeroLog) Log(level maulogger.Level, parts ...interface{}) {
+ m.Logger.WithLevel(mauToZeroLevel(level)).Msg(fmt.Sprint(parts...))
+}
+
+func (m MauZeroLog) Logln(level maulogger.Level, parts ...interface{}) {
+ m.Logger.WithLevel(mauToZeroLevel(level)).Msg(strings.TrimSuffix(fmt.Sprintln(parts...), "\n"))
+}
+
+func (m MauZeroLog) Logf(level maulogger.Level, message string, args ...interface{}) {
+ m.Logger.WithLevel(mauToZeroLevel(level)).Msg(fmt.Sprintf(message, args...))
+}
+
+func (m MauZeroLog) Logfln(level maulogger.Level, message string, args ...interface{}) {
+ m.Logger.WithLevel(mauToZeroLevel(level)).Msg(fmt.Sprintf(message, args...))
+}
+
+func (m MauZeroLog) Debug(parts ...interface{}) {
+ m.Logger.Debug().Msg(fmt.Sprint(parts...))
+}
+
+func (m MauZeroLog) Debugln(parts ...interface{}) {
+ m.Logger.Debug().Msg(strings.TrimSuffix(fmt.Sprintln(parts...), "\n"))
+}
+
+func (m MauZeroLog) Debugf(message string, args ...interface{}) {
+ m.Logger.Debug().Msg(fmt.Sprintf(message, args...))
+}
+
+func (m MauZeroLog) Debugfln(message string, args ...interface{}) {
+ m.Logger.Debug().Msg(fmt.Sprintf(message, args...))
+}
+
+func (m MauZeroLog) Info(parts ...interface{}) {
+ m.Logger.Info().Msg(fmt.Sprint(parts...))
+}
+
+func (m MauZeroLog) Infoln(parts ...interface{}) {
+ m.Logger.Info().Msg(strings.TrimSuffix(fmt.Sprintln(parts...), "\n"))
+}
+
+func (m MauZeroLog) Infof(message string, args ...interface{}) {
+ m.Logger.Info().Msg(fmt.Sprintf(message, args...))
+}
+
+func (m MauZeroLog) Infofln(message string, args ...interface{}) {
+ m.Logger.Info().Msg(fmt.Sprintf(message, args...))
+}
+
+func (m MauZeroLog) Warn(parts ...interface{}) {
+ m.Logger.Warn().Msg(fmt.Sprint(parts...))
+}
+
+func (m MauZeroLog) Warnln(parts ...interface{}) {
+ m.Logger.Warn().Msg(strings.TrimSuffix(fmt.Sprintln(parts...), "\n"))
+}
+
+func (m MauZeroLog) Warnf(message string, args ...interface{}) {
+ m.Logger.Warn().Msg(fmt.Sprintf(message, args...))
+}
+
+func (m MauZeroLog) Warnfln(message string, args ...interface{}) {
+ m.Logger.Warn().Msg(fmt.Sprintf(message, args...))
+}
+
+func (m MauZeroLog) Error(parts ...interface{}) {
+ m.Logger.Error().Msg(fmt.Sprint(parts...))
+}
+
+func (m MauZeroLog) Errorln(parts ...interface{}) {
+ m.Logger.Error().Msg(strings.TrimSuffix(fmt.Sprintln(parts...), "\n"))
+}
+
+func (m MauZeroLog) Errorf(message string, args ...interface{}) {
+ m.Logger.Error().Msg(fmt.Sprintf(message, args...))
+}
+
+func (m MauZeroLog) Errorfln(message string, args ...interface{}) {
+ m.Logger.Error().Msg(fmt.Sprintf(message, args...))
+}
+
+func (m MauZeroLog) Fatal(parts ...interface{}) {
+ m.Logger.WithLevel(zerolog.FatalLevel).Msg(fmt.Sprint(parts...))
+}
+
+func (m MauZeroLog) Fatalln(parts ...interface{}) {
+ m.Logger.WithLevel(zerolog.FatalLevel).Msg(strings.TrimSuffix(fmt.Sprintln(parts...), "\n"))
+}
+
+func (m MauZeroLog) Fatalf(message string, args ...interface{}) {
+ m.Logger.WithLevel(zerolog.FatalLevel).Msg(fmt.Sprintf(message, args...))
+}
+
+func (m MauZeroLog) Fatalfln(message string, args ...interface{}) {
+ m.Logger.WithLevel(zerolog.FatalLevel).Msg(fmt.Sprintf(message, args...))
+}
diff --git a/vendor/maunium.net/go/maulogger/v2/maulogadapt/zeromaulog.go b/vendor/maunium.net/go/maulogger/v2/maulogadapt/zeromaulog.go
new file mode 100644
index 00000000..1a275e7d
--- /dev/null
+++ b/vendor/maunium.net/go/maulogger/v2/maulogadapt/zeromaulog.go
@@ -0,0 +1,73 @@
+// Copyright (c) 2023 Tulir Asokan
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package maulogadapt
+
+import (
+ "bytes"
+
+ "github.com/rs/zerolog"
+ "github.com/tidwall/gjson"
+ "github.com/tidwall/sjson"
+
+ "maunium.net/go/maulogger/v2"
+)
+
+// ZeroMauLog is a simple wrapper for a maulogger that can be set as the output writer for zerolog.
+type ZeroMauLog struct {
+ maulogger.Logger
+}
+
+func MauAsZero(log maulogger.Logger) *zerolog.Logger {
+ zero := zerolog.New(&ZeroMauLog{log})
+ return &zero
+}
+
+var _ zerolog.LevelWriter = (*ZeroMauLog)(nil)
+
+func (z *ZeroMauLog) Write(p []byte) (n int, err error) {
+ return 0, nil
+}
+
+func (z *ZeroMauLog) WriteLevel(level zerolog.Level, p []byte) (n int, err error) {
+ var mauLevel maulogger.Level
+ switch level {
+ case zerolog.DebugLevel:
+ mauLevel = maulogger.LevelDebug
+ case zerolog.InfoLevel, zerolog.NoLevel:
+ mauLevel = maulogger.LevelInfo
+ case zerolog.WarnLevel:
+ mauLevel = maulogger.LevelWarn
+ case zerolog.ErrorLevel:
+ mauLevel = maulogger.LevelError
+ case zerolog.FatalLevel, zerolog.PanicLevel:
+ mauLevel = maulogger.LevelFatal
+ case zerolog.Disabled, zerolog.TraceLevel:
+ fallthrough
+ default:
+ return 0, nil
+ }
+ p = bytes.TrimSuffix(p, []byte{'\n'})
+ msg := gjson.GetBytes(p, zerolog.MessageFieldName).Str
+
+ p, err = sjson.DeleteBytes(p, zerolog.MessageFieldName)
+ if err != nil {
+ return
+ }
+ p, err = sjson.DeleteBytes(p, zerolog.LevelFieldName)
+ if err != nil {
+ return
+ }
+ p, err = sjson.DeleteBytes(p, zerolog.TimestampFieldName)
+ if err != nil {
+ return
+ }
+ if len(p) > 2 {
+ msg += " " + string(p)
+ }
+ z.Log(mauLevel, msg)
+ return len(p), nil
+}
diff --git a/vendor/maunium.net/go/maulogger/v2/sublogger.go b/vendor/maunium.net/go/maulogger/v2/sublogger.go
new file mode 100644
index 00000000..b3326908
--- /dev/null
+++ b/vendor/maunium.net/go/maulogger/v2/sublogger.go
@@ -0,0 +1,216 @@
+// mauLogger - A logger for Go programs
+// Copyright (c) 2016-2021 Tulir Asokan
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package maulogger
+
+import (
+ "fmt"
+)
+
+type Sublogger struct {
+ topLevel *BasicLogger
+ parent Logger
+ Module string
+ DefaultLevel Level
+ metadata map[string]interface{}
+}
+
+// Subm creates a Sublogger
+func (log *BasicLogger) Subm(module string, metadata map[string]interface{}) Logger {
+ return &Sublogger{
+ topLevel: log,
+ parent: log,
+ Module: module,
+ DefaultLevel: LevelInfo,
+ metadata: metadata,
+ }
+}
+
+func (log *BasicLogger) Sub(module string) Logger {
+ return log.Subm(module, map[string]interface{}{})
+}
+
+// WithDefaultLevel creates a Sublogger with the same Module but different DefaultLevel
+func (log *BasicLogger) WithDefaultLevel(lvl Level) Logger {
+ return log.DefaultSub.WithDefaultLevel(lvl)
+}
+
+func (log *Sublogger) GetParent() Logger {
+ return log.parent
+}
+
+// Sub creates a Sublogger
+func (log *Sublogger) Subm(module string, metadata map[string]interface{}) Logger {
+ if len(module) > 0 {
+ module = fmt.Sprintf("%s/%s", log.Module, module)
+ } else {
+ module = log.Module
+ }
+
+ return &Sublogger{
+ topLevel: log.topLevel,
+ parent: log,
+ Module: module,
+ DefaultLevel: log.DefaultLevel,
+ metadata: metadata,
+ }
+}
+
+func (log *Sublogger) Sub(module string) Logger {
+ return log.Subm(module, map[string]interface{}{})
+}
+
+// WithDefaultLevel creates a Sublogger with the same Module but different DefaultLevel
+func (log *Sublogger) WithDefaultLevel(lvl Level) Logger {
+ return &Sublogger{
+ topLevel: log.topLevel,
+ parent: log.parent,
+ Module: log.Module,
+ DefaultLevel: lvl,
+ }
+}
+
+// SetModule changes the module name of this Sublogger
+func (log *Sublogger) SetModule(mod string) {
+ log.Module = mod
+}
+
+// SetDefaultLevel changes the default logging level of this Sublogger
+func (log *Sublogger) SetDefaultLevel(lvl Level) {
+ log.DefaultLevel = lvl
+}
+
+// SetParent changes the parent of this Sublogger
+func (log *Sublogger) SetParent(parent *BasicLogger) {
+ log.topLevel = parent
+}
+
+//Write ...
+func (log *Sublogger) Write(p []byte) (n int, err error) {
+ log.topLevel.Raw(log.DefaultLevel, log.metadata, log.Module, string(p))
+ return len(p), nil
+}
+
+// Log formats the given parts with fmt.Sprint and logs the result with the given level
+func (log *Sublogger) Log(level Level, parts ...interface{}) {
+ log.topLevel.Raw(level, log.metadata, log.Module, fmt.Sprint(parts...))
+}
+
+// Logln formats the given parts with fmt.Sprintln and logs the result with the given level
+func (log *Sublogger) Logln(level Level, parts ...interface{}) {
+ log.topLevel.Raw(level, log.metadata, log.Module, fmt.Sprintln(parts...))
+}
+
+// Logf formats the given message and args with fmt.Sprintf and logs the result with the given level
+func (log *Sublogger) Logf(level Level, message string, args ...interface{}) {
+ log.topLevel.Raw(level, log.metadata, log.Module, fmt.Sprintf(message, args...))
+}
+
+// Logfln formats the given message and args with fmt.Sprintf, appends a newline and logs the result with the given level
+func (log *Sublogger) Logfln(level Level, message string, args ...interface{}) {
+ log.topLevel.Raw(level, log.metadata, log.Module, fmt.Sprintf(message+"\n", args...))
+}
+
+// Debug formats the given parts with fmt.Sprint and logs the result with the Debug level
+func (log *Sublogger) Debug(parts ...interface{}) {
+ log.topLevel.Raw(LevelDebug, log.metadata, log.Module, fmt.Sprint(parts...))
+}
+
+// Debugln formats the given parts with fmt.Sprintln and logs the result with the Debug level
+func (log *Sublogger) Debugln(parts ...interface{}) {
+ log.topLevel.Raw(LevelDebug, log.metadata, log.Module, fmt.Sprintln(parts...))
+}
+
+// Debugf formats the given message and args with fmt.Sprintf and logs the result with the Debug level
+func (log *Sublogger) Debugf(message string, args ...interface{}) {
+ log.topLevel.Raw(LevelDebug, log.metadata, log.Module, fmt.Sprintf(message, args...))
+}
+
+// Debugfln formats the given message and args with fmt.Sprintf, appends a newline and logs the result with the Debug level
+func (log *Sublogger) Debugfln(message string, args ...interface{}) {
+ log.topLevel.Raw(LevelDebug, log.metadata, log.Module, fmt.Sprintf(message+"\n", args...))
+}
+
+// Info formats the given parts with fmt.Sprint and logs the result with the Info level
+func (log *Sublogger) Info(parts ...interface{}) {
+ log.topLevel.Raw(LevelInfo, log.metadata, log.Module, fmt.Sprint(parts...))
+}
+
+// Infoln formats the given parts with fmt.Sprintln and logs the result with the Info level
+func (log *Sublogger) Infoln(parts ...interface{}) {
+ log.topLevel.Raw(LevelInfo, log.metadata, log.Module, fmt.Sprintln(parts...))
+}
+
+// Infof formats the given message and args with fmt.Sprintf and logs the result with the Info level
+func (log *Sublogger) Infof(message string, args ...interface{}) {
+ log.topLevel.Raw(LevelInfo, log.metadata, log.Module, fmt.Sprintf(message, args...))
+}
+
+// Infofln formats the given message and args with fmt.Sprintf, appends a newline and logs the result with the Info level
+func (log *Sublogger) Infofln(message string, args ...interface{}) {
+ log.topLevel.Raw(LevelInfo, log.metadata, log.Module, fmt.Sprintf(message+"\n", args...))
+}
+
+// Warn formats the given parts with fmt.Sprint and logs the result with the Warn level
+func (log *Sublogger) Warn(parts ...interface{}) {
+ log.topLevel.Raw(LevelWarn, log.metadata, log.Module, fmt.Sprint(parts...))
+}
+
+// Warnln formats the given parts with fmt.Sprintln and logs the result with the Warn level
+func (log *Sublogger) Warnln(parts ...interface{}) {
+ log.topLevel.Raw(LevelWarn, log.metadata, log.Module, fmt.Sprintln(parts...))
+}
+
+// Warnf formats the given message and args with fmt.Sprintf and logs the result with the Warn level
+func (log *Sublogger) Warnf(message string, args ...interface{}) {
+ log.topLevel.Raw(LevelWarn, log.metadata, log.Module, fmt.Sprintf(message, args...))
+}
+
+// Warnfln formats the given message and args with fmt.Sprintf, appends a newline and logs the result with the Warn level
+func (log *Sublogger) Warnfln(message string, args ...interface{}) {
+ log.topLevel.Raw(LevelWarn, log.metadata, log.Module, fmt.Sprintf(message+"\n", args...))
+}
+
+// Error formats the given parts with fmt.Sprint and logs the result with the Error level
+func (log *Sublogger) Error(parts ...interface{}) {
+ log.topLevel.Raw(LevelError, log.metadata, log.Module, fmt.Sprint(parts...))
+}
+
+// Errorln formats the given parts with fmt.Sprintln and logs the result with the Error level
+func (log *Sublogger) Errorln(parts ...interface{}) {
+ log.topLevel.Raw(LevelError, log.metadata, log.Module, fmt.Sprintln(parts...))
+}
+
+// Errorf formats the given message and args with fmt.Sprintf and logs the result with the Error level
+func (log *Sublogger) Errorf(message string, args ...interface{}) {
+ log.topLevel.Raw(LevelError, log.metadata, log.Module, fmt.Sprintf(message, args...))
+}
+
+// Errorfln formats the given message and args with fmt.Sprintf, appends a newline and logs the result with the Error level
+func (log *Sublogger) Errorfln(message string, args ...interface{}) {
+ log.topLevel.Raw(LevelError, log.metadata, log.Module, fmt.Sprintf(message+"\n", args...))
+}
+
+// Fatal formats the given parts with fmt.Sprint and logs the result with the Fatal level
+func (log *Sublogger) Fatal(parts ...interface{}) {
+ log.topLevel.Raw(LevelFatal, log.metadata, log.Module, fmt.Sprint(parts...))
+}
+
+// Fatalln formats the given parts with fmt.Sprintln and logs the result with the Fatal level
+func (log *Sublogger) Fatalln(parts ...interface{}) {
+ log.topLevel.Raw(LevelFatal, log.metadata, log.Module, fmt.Sprintln(parts...))
+}
+
+// Fatalf formats the given message and args with fmt.Sprintf and logs the result with the Fatal level
+func (log *Sublogger) Fatalf(message string, args ...interface{}) {
+ log.topLevel.Raw(LevelFatal, log.metadata, log.Module, fmt.Sprintf(message, args...))
+}
+
+// Fatalfln formats the given message and args with fmt.Sprintf, appends a newline and logs the result with the Fatal level
+func (log *Sublogger) Fatalfln(message string, args ...interface{}) {
+ log.topLevel.Raw(LevelFatal, log.metadata, log.Module, fmt.Sprintf(message+"\n", args...))
+}
diff --git a/vendor/maunium.net/go/maulogger/v2/writer.go b/vendor/maunium.net/go/maulogger/v2/writer.go
new file mode 100644
index 00000000..8852b857
--- /dev/null
+++ b/vendor/maunium.net/go/maulogger/v2/writer.go
@@ -0,0 +1,78 @@
+// mauLogger - A logger for Go programs
+// Copyright (c) 2021 Tulir Asokan
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package maulogger
+
+import (
+ "bytes"
+ "io"
+ "sync"
+)
+
+// LogWriter is a buffered io.Writer that writes lines to a Logger.
+type LogWriter struct {
+ log Logger
+ lock sync.Mutex
+ level Level
+ buf bytes.Buffer
+}
+
+func (log *BasicLogger) Writer(level Level) io.WriteCloser {
+ return &LogWriter{
+ log: log,
+ level: level,
+ }
+}
+
+func (log *Sublogger) Writer(level Level) io.WriteCloser {
+ return &LogWriter{
+ log: log,
+ level: level,
+ }
+}
+
+func (lw *LogWriter) writeLine(data []byte) {
+ if lw.buf.Len() == 0 {
+ if len(data) == 0 {
+ return
+ }
+ lw.log.Logln(lw.level, string(data))
+ } else {
+ lw.buf.Write(data)
+ lw.log.Logln(lw.level, lw.buf.String())
+ lw.buf.Reset()
+ }
+}
+
+// Write will write lines from the given data to the buffer. If the data doesn't end with a line break,
+// everything after the last line break will be buffered until the next Write or Close call.
+func (lw *LogWriter) Write(data []byte) (int, error) {
+ lw.lock.Lock()
+ newline := bytes.IndexByte(data, '\n')
+ if newline == len(data)-1 {
+ lw.writeLine(data[:len(data)-1])
+ } else if newline < 0 {
+ lw.buf.Write(data)
+ } else {
+ lines := bytes.Split(data, []byte("\n"))
+ for _, line := range lines[:len(lines)-1] {
+ lw.writeLine(line)
+ }
+ lw.buf.Write(lines[len(lines)-1])
+ }
+ lw.lock.Unlock()
+ return len(data), nil
+}
+
+// Close will flush remaining data in the buffer into the logger.
+func (lw *LogWriter) Close() error {
+ lw.lock.Lock()
+ lw.log.Logln(lw.level, lw.buf.String())
+ lw.buf.Reset()
+ lw.lock.Unlock()
+ return nil
+}
diff --git a/vendor/maunium.net/go/mautrix/.editorconfig b/vendor/maunium.net/go/mautrix/.editorconfig
new file mode 100644
index 00000000..21d312a1
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/.editorconfig
@@ -0,0 +1,12 @@
+root = true
+
+[*]
+indent_style = tab
+indent_size = 4
+end_of_line = lf
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
+
+[*.{yaml,yml}]
+indent_style = space
diff --git a/vendor/maunium.net/go/mautrix/.gitignore b/vendor/maunium.net/go/mautrix/.gitignore
new file mode 100644
index 00000000..f37a7d0c
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/.gitignore
@@ -0,0 +1,4 @@
+.idea/
+.vscode/
+*.db
+*.log
diff --git a/vendor/maunium.net/go/mautrix/.pre-commit-config.yaml b/vendor/maunium.net/go/mautrix/.pre-commit-config.yaml
new file mode 100644
index 00000000..1ef386ea
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/.pre-commit-config.yaml
@@ -0,0 +1,15 @@
+repos:
+ - repo: https://github.com/pre-commit/pre-commit-hooks
+ rev: v4.4.0
+ hooks:
+ - id: trailing-whitespace
+ exclude_types: [markdown]
+ - id: end-of-file-fixer
+ - id: check-yaml
+ - id: check-added-large-files
+
+ - repo: https://github.com/tekwizely/pre-commit-golang
+ rev: v1.0.0-rc.1
+ hooks:
+ - id: go-imports-repo
+ - id: go-vet-repo-mod
diff --git a/vendor/maunium.net/go/mautrix/CHANGELOG.md b/vendor/maunium.net/go/mautrix/CHANGELOG.md
new file mode 100644
index 00000000..07f60013
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/CHANGELOG.md
@@ -0,0 +1,612 @@
+## v0.15.0 (2023-03-16)
+
+### beta.3 (2023-03-15)
+
+* **Breaking change *(appservice)*** Removed `Load()` and `AppService.Init()`
+ functions. The struct should just be created with `Create()` and the relevant
+ fields should be filled manually.
+* **Breaking change *(appservice)*** Removed public `HomeserverURL` field and
+ replaced it with a `SetHomeserverURL` method.
+* *(appservice)* Added support for unix sockets for homeserver URL and
+ appservice HTTP server.
+* *(client)* Changed request logging to log durations as floats instead of
+ strings (using zerolog's `Dur()`, so the exact output can be configured).
+* *(bridge)* Changed zerolog to use nanosecond precision timestamps.
+* *(crypto)* Added message index to log after encrypting/decrypting megolm
+ events, and when failing to decrypt due to duplicate index.
+* *(sqlstatestore)* Fixed warning log for rooms that don't have encryption
+ enabled.
+
+### beta.2 (2023-03-02)
+
+* *(bridge)* Fixed building with `nocrypto` tag.
+* *(bridge)* Fixed legacy logging config migration not disabling file writer
+ when `file_name_format` was empty.
+* *(bridge)* Added option to require room power level to run commands.
+* *(event)* Added structs for [MSC3952]: Intentional Mentions.
+* *(util/variationselector)* Added `FullyQualify` method to add necessary emoji
+ variation selectors without adding all possible ones.
+
+[MSC3952]: https://github.com/matrix-org/matrix-spec-proposals/pull/3952
+
+### beta.1 (2023-02-24)
+
+* Bumped minimum Go version to 1.19.
+* **Breaking changes**
+ * *(all)* Switched to zerolog for logging.
+ * The `Client` and `Bridge` structs still include a legacy logger for
+ backwards compatibility.
+ * *(client, appservice)* Moved `SQLStateStore` from appservice module to the
+ top-level (client) module.
+ * *(client, appservice)* Removed unused `Typing` map in `SQLStateStore`.
+ * *(client)* Removed unused `SaveRoom` and `LoadRoom` methods in `Storer`.
+ * *(client, appservice)* Removed deprecated `SendVideo` and `SendImage` methods.
+ * *(client)* Replaced `AppServiceUserID` field with `SetAppServiceUserID` boolean.
+ The `UserID` field is used as the value for the query param.
+ * *(crypto)* Renamed `GobStore` to `MemoryStore` and removed the file saving
+ features. The data can still be persisted, but the persistence part must be
+ implemented separately.
+ * *(crypto)* Removed deprecated `DeviceIdentity` alias
+ (renamed to `id.Device` long ago).
+ * *(client)* Removed `Stringifable` interface as it's the same as `fmt.Stringer`.
+* *(client)* Renamed `Storer` interface to `SyncStore`. A type alias exists for
+ backwards-compatibility.
+* *(crypto/cryptohelper)* Added package for a simplified crypto interface for clients.
+* *(example)* Added e2ee support to example using crypto helper.
+* *(client)* Changed default syncer to stop syncing on `M_UNKNOWN_TOKEN` errors.
+
+## v0.14.0 (2023-02-16)
+
+* **Breaking change *(format)*** Refactored the HTML parser `Context` to have
+ more data.
+* *(id)* Fixed escaping path components when forming matrix.to URLs
+ or `matrix:` URIs.
+* *(bridge)* Bumped default timeouts for decrypting incoming messages.
+* *(bridge)* Added `RawArgs` to commands to allow accessing non-split input.
+* *(bridge)* Added `ReplyAdvanced` to commands to allow setting markdown
+ settings.
+* *(event)* Added `notifications` key to `PowerLevelEventContent`.
+* *(event)* Changed `SetEdit` to cut off edit fallback if the message is long.
+* *(util)* Added `SyncMap` as a simple generic wrapper for a map with a mutex.
+* *(util)* Added `ReturnableOnce` as a wrapper for `sync.Once` with a return
+ value.
+
+## v0.13.0 (2023-01-16)
+
+* **Breaking change:** Removed `IsTyping` and `SetTyping` in `appservice.StateStore`
+ and removed the `TypingStateStore` struct implementing those methods.
+* **Breaking change:** Removed legacy fields in Beeper MSS events.
+* Added knocked rooms to sync response structs.
+* Added wrapper for `/timestamp_to_event` endpoint added in Matrix v1.6.
+* Fixed MSC3870 uploads not failing properly after using up the max retry count.
+* Fixed parsing non-positive ordered list start positions in HTML parser.
+
+## v0.12.4 (2022-12-16)
+
+* Added `SendReceipt` to support private read receipts and thread receipts in
+ the same function. `MarkReadWithContent` is now deprecated.
+* Changed media download methods to return errors if the server returns a
+ non-2xx status code.
+* Removed legacy `sql_store_upgrade.Upgrade` method. Using `store.DB.Upgrade()`
+ after `NewSQLCryptoStore(...)` is recommended instead (the bridge module does
+ this automatically).
+* Added missing `suggested` field to `m.space.child` content struct.
+* Added `device_unused_fallback_key_types` to `/sync` response and appservice
+ transaction structs.
+* Changed `ReqSetReadMarkers` to omit empty fields.
+* Changed bridge configs to force `sqlite3-fk-wal` instead of `sqlite3`.
+* Updated bridge helper to close database connection when stopping.
+* Fixed read receipt and account data endpoints sending `null` instead of an
+ empty object as the body when content isn't provided.
+
+## v0.12.3 (2022-11-16)
+
+* **Breaking change:** Added logging for row iteration in the dbutil package.
+ This changes the return type of `Query` methods from `*sql.Rows` to a new
+ `dbutil.Rows` interface.
+* Added flag to disable wrapping database upgrades in a transaction (e.g. to
+ allow setting `PRAGMA`s for advanced table mutations on SQLite).
+* Deprecated `MessageEventContent.GetReplyTo` in favor of directly using
+ `RelatesTo.GetReplyTo`. RelatesTo methods are nil-safe, so checking if
+ RelatesTo is nil is not necessary for using those methods.
+* Added wrapper for space hierarchyendpoint (thanks to [@mgcm] in [#100]).
+* Added bridge config option to handle transactions asynchronously.
+* Added separate channels for to-device events in appservice transaction
+ handler to avoid blocking to-device events behind normal events.
+* Added `RelatesTo.GetNonFallbackReplyTo` utility method to get the reply event
+ ID, unless the reply is a thread fallback.
+* Added `event.TextToHTML` as an utility method to HTML-escape a string and
+ replace newlines with `<br/>`.
+* Added check to bridge encryption helper to make sure the e2ee keys are still
+ on the server. Synapse is known to sometimes lose keys randomly.
+* Changed bridge crypto syncer to crash on `M_UNKNOWN_TOKEN` errors instead of
+ retrying forever pointlessly.
+* Fixed verifying signatures of fallback one-time keys.
+
+[@mgcm]: https://github.com/mgcm
+[#100]: https://github.com/mautrix/go/pull/100
+
+## v0.12.2 (2022-10-16)
+
+* Added utility method to redact bridge commands.
+* Added thread ID field to read receipts to match Matrix v1.4 changes.
+* Added automatic fetching of media repo config at bridge startup to make it
+ easier for bridges to check homeserver media size limits.
+* Added wrapper for the `/register/available` endpoint.
+* Added custom user agent to all requests mautrix-go makes. The value can be
+ customized by changing the `DefaultUserAgent` variable.
+* Implemented [MSC3664], [MSC3862] and [MSC3873] in the push rule evaluator.
+* Added workaround for potential race conditions in OTK uploads when using
+ appservice encryption ([MSC3202]).
+* Fixed generating registrations to use `.+` instead of `[0-9]+` in the
+ username regex.
+* Fixed panic in megolm session listing methods if the store contains withheld
+ key entries.
+* Fixed missing header in bridge command help messages.
+
+[MSC3664]: https://github.com/matrix-org/matrix-spec-proposals/pull/3664
+[MSC3862]: https://github.com/matrix-org/matrix-spec-proposals/pull/3862
+[MSC3873]: https://github.com/matrix-org/matrix-spec-proposals/pull/3873
+
+## v0.12.1 (2022-09-16)
+
+* Bumped minimum Go version to 1.18.
+* Added `omitempty` for a bunch of fields in response structs to make them more
+ usable for server implementations.
+* Added `util.RandomToken` to generate GitHub-style access tokens with checksums.
+* Added utilities to call the push gateway API.
+* Added `unread_notifications` and [MSC2654] `unread_count` fields to /sync
+ response structs.
+* Implemented [MSC3870] for uploading and downloading media directly to/from an
+ external media storage like S3.
+* Fixed dbutil database ownership checks on SQLite.
+* Fixed typo in unauthorized encryption key withheld code
+ (`m.unauthorized` -> `m.unauthorised`).
+* Fixed [MSC2409] support to have a separate field for to-device events.
+
+[MSC2654]: https://github.com/matrix-org/matrix-spec-proposals/pull/2654
+[MSC3870]: https://github.com/matrix-org/matrix-spec-proposals/pull/3870
+
+## v0.12.0 (2022-08-16)
+
+* **Breaking change:** Switched `Client.UserTyping` to take a `time.Duration`
+ instead of raw `int64` milliseconds.
+* **Breaking change:** Removed custom reply relation type and switched to using
+ the wire format (nesting in `m.in_reply_to`).
+* Added device ID to appservice OTK count map to match updated [MSC3202].
+ This is also a breaking change, but the previous incorrect behavior wasn't
+ implemented by anything other than mautrix-syncproxy/imessage.
+* (There are probably other breaking changes too).
+* Added database utility and schema upgrade framework
+ * Originally from mautrix-whatsapp, but usable for non-bridges too
+ * Includes connection wrapper to log query durations and mutate queries for
+ SQLite compatibility (replacing `$x` with `?x`).
+* Added bridge utilities similar to mautrix-python. Currently includes:
+ * Crypto helper
+ * Startup flow
+ * Command handling and some standard commands
+ * Double puppeting things
+ * Generic parts of config, basic config validation
+ * Appservice SQL state store
+* Added alternative markdown spoiler parsing extension that doesn't support
+ reasons, but works better otherwise.
+* Added Discord underline markdown parsing extension (`_foo_` -> <u>foo</u>).
+* Added support for parsing spoilers and color tags in the HTML parser.
+* Added support for mutating plain text nodes in the HTML parser.
+* Added room version field to the create room request struct.
+* Added empty JSON object as default request body for all non-GET requests.
+* Added wrapper for `/capabilities` endpoint.
+* Added `omitempty` markers for lots of structs to make the structs easier to
+ use on the server side too.
+* Added support for registering to-device event handlers via the default
+ Syncer's `OnEvent` and `OnEventType` methods.
+* Fixed `CreateEventContent` using the wrong field name for the room version
+ field.
+* Fixed `StopSync` not immediately cancelling the sync loop if it was sleeping
+ after a failed sync.
+* Fixed `GetAvatarURL` always returning the current user's avatar instead of
+ the specified user's avatar (thanks to [@nightmared] in [#83]).
+* Improved request logging and added new log when a request finishes.
+* Crypto store improvements:
+ * Deleted devices are now kept in the database.
+ * Made ValidateMessageIndex atomic.
+* Moved `appservice.RandomString` to the `util` package and made it use
+ `crypto/rand` instead of `math/rand`.
+* Significantly improved cross-signing validation code.
+ * There are now more options for required trust levels,
+ e.g. you can set `SendKeysMinTrust` to `id.TrustStateCrossSignedTOFU`
+ to trust the first cross-signing master key seen and require all devices
+ to be signed by that key.
+ * Trust state of incoming messages is automatically resolved and stored in
+ `evt.Mautrix.TrustState`. This can be used to reject incoming messages from
+ untrusted devices.
+
+[@nightmared]: https://github.com/nightmared
+[#83]: https://github.com/mautrix/go/pull/83
+
+## v0.11.1 (2023-01-15)
+
+* Fixed parsing non-positive ordered list start positions in HTML parser
+ (backport of the same fix in v0.13.0).
+
+## v0.11.0 (2022-05-16)
+
+* Bumped minimum Go version to 1.17.
+* Switched from `/r0` to `/v3` paths everywhere.
+ * The new `v3` paths are implemented since Synapse 1.48, Dendrite 0.6.5, and
+ Conduit 0.4.0. Servers older than these are no longer supported.
+* Switched from blackfriday to goldmark for markdown parsing in the `format`
+ module and added spoiler syntax.
+* Added `EncryptInPlace` and `DecryptInPlace` methods for attachment encryption.
+ In most cases the plain/ciphertext is not necessary after en/decryption, so
+ the old `Encrypt` and `Decrypt` are deprecated.
+* Added wrapper for `/rooms/.../aliases`.
+* Added utility for adding/removing emoji variation selectors to match
+ recommendations on reactions in Matrix.
+* Added support for async media uploads ([MSC2246]).
+* Added automatic sleep when receiving 429 error
+ (thanks to [@ownaginatious] in [#44]).
+* Added support for parsing spec version numbers from the `/versions` endpoint.
+* Removed unstable prefixed constant used for appservice login.
+* Fixed URL encoding not working correctly in some cases.
+
+[MSC2246]: https://github.com/matrix-org/matrix-spec-proposals/pull/2246
+[@ownaginatious]: https://github.com/ownaginatious
+[#44]: https://github.com/mautrix/go/pull/44
+
+## v0.10.12 (2022-03-16)
+
+* Added option to use a different `Client` to send invites in
+ `IntentAPI.EnsureJoined`.
+* Changed `MessageEventContent` struct to omit empty `msgtype`s in the output
+ JSON, as sticker events shouldn't have that field.
+* Fixed deserializing the `thumbnail_file` field in `FileInfo`.
+* Fixed bug that broke `SQLCryptoStore.FindDeviceByKey`.
+
+## v0.10.11 (2022-02-16)
+
+* Added automatic updating of state store from `IntentAPI` calls.
+* Added option to ignore cache in `IntentAPI.EnsureJoined`.
+* Added `GetURLPreview` as a wrapper for the `/preview_url` media repo endpoint.
+* Moved base58 module inline to avoid pulling in btcd as a dependency.
+
+## v0.10.10 (2022-01-16)
+
+* Added event types and content structs for server ACLs and moderation policy
+ lists (thanks to [@qua3k] in [#59] and [#60]).
+* Added optional parameter to `Client.LeaveRoom` to pass a `reason` field.
+
+[#59]: https://github.com/mautrix/go/pull/59
+[#60]: https://github.com/mautrix/go/pull/60
+
+## v0.10.9 (2022-01-04)
+
+* **Breaking change:** Changed `Messages()` to take a filter as a parameter
+ instead of using the syncer's filter (thanks to [@qua3k] in [#55] and [#56]).
+ * The previous filter behavior was completely broken, as it sent a whole
+ filter instead of just a RoomEventFilter.
+ * Passing `nil` as the filter is fine and will disable filtering
+ (which is equivalent to what it did before with the invalid filter).
+* Added `Context()` wrapper for the `/context` API (thanks to [@qua3k] in [#54]).
+* Added utility for converting media files with ffmpeg.
+
+[#54]: https://github.com/mautrix/go/pull/54
+[#55]: https://github.com/mautrix/go/pull/55
+[#56]: https://github.com/mautrix/go/pull/56
+[@qua3k]: https://github.com/qua3k
+
+## v0.10.8 (2021-12-30)
+
+* Added `OlmSession.Describe()` to wrap `olm_session_describe`.
+* Added trace logs to log olm session descriptions when encrypting/decrypting
+ to-device messages.
+* Added space event types and content structs.
+* Added support for power level content override field in `CreateRoom`.
+* Fixed ordering of olm sessions which would cause an old session to be used in
+ some cases even after a client created a new session.
+
+## v0.10.7 (2021-12-16)
+
+* Changed `Client.RedactEvent` to allow arbitrary fields in redaction request.
+
+## v0.10.5 (2021-12-06)
+
+* Fixed websocket disconnection not clearing all pending requests.
+* Added `OlmMachine.SendRoomKeyRequest` as a more direct way of sending room
+ key requests.
+* Added automatic Olm session recreation if an incoming message fails to decrypt.
+* Changed `Login` to only omit request content from logs if there's a password
+ or token (appservice logins don't have sensitive content).
+
+## v0.10.4 (2021-11-25)
+
+* Added `reason` field to invite and unban requests
+ (thanks to [@ptman] in [#48]).
+* Fixed `AppService.HasWebsocket()` returning `true` even after websocket has
+ disconnected.
+
+[@ptman]: https://github.com/ptman
+[#48]: https://github.com/mautrix/go/pull/48
+
+## v0.10.3 (2021-11-18)
+
+* Added logs about incoming appservice transactions.
+* Added support for message send checkpoints (as HTTP requests, similar to the
+ bridge state reporting system).
+
+## v0.10.2 (2021-11-15)
+
+* Added utility method for finding the first supported login flow matching any
+ of the given types.
+* Updated registering appservice ghosts to use `inhibit_login` flag to prevent
+ lots of unnecessary access tokens from being created.
+ * If you want to log in as an appservice ghost, you should use [MSC2778]'s
+ appservice login (e.g. like [mautrix-whatsapp does for e2be](https://github.com/mautrix/whatsapp/blob/v0.2.1/crypto.go#L143-L149)).
+
+## v0.10.1 (2021-11-05)
+
+* Removed direct dependency on `pq`
+ * In order to use some more efficient queries on postgres, you must set
+ `crypto.PostgresArrayWrapper = pq.Array` if you want to use both postgres
+ and e2ee.
+* Added temporary hack to ignore state events with the MSC2716 historical flag
+ (to be removed after [matrix-org/synapse#11265] is merged)
+* Added received transaction acknowledgements for websocket appservice
+ transactions.
+* Added automatic fallback to move `prev_content` from top level to the
+ standard location inside `unsigned`.
+
+[matrix-org/synapse#11265]: https://github.com/matrix-org/synapse/pull/11265
+
+## v0.9.31 (2021-10-27)
+
+* Added `SetEdit` utility function for `MessageEventContent`.
+
+## v0.9.30 (2021-10-26)
+
+* Added wrapper for [MSC2716]'s `/batch_send` endpoint.
+* Added `MarshalJSON` method for `Event` struct to prevent empty unsigned
+ structs from being included in the JSON.
+
+[MSC2716]: https://github.com/matrix-org/matrix-spec-proposals/pull/2716
+
+## v0.9.29 (2021-09-30)
+
+* Added `client.State` method to get full room state.
+* Added bridge info structs and event types ([MSC2346]).
+* Made response handling more customizable.
+* Fixed type of `AuthType` constants.
+
+[MSC2346]: https://github.com/matrix-org/matrix-spec-proposals/pull/2346
+
+## v0.9.28 (2021-09-30)
+
+* Added `X-Mautrix-Process-ID` to appservice websocket headers to help debug
+ issues where multiple instances are connecting to the server at the same time.
+
+## v0.9.27 (2021-09-23)
+
+* Fixed Go 1.14 compatibility (broken in v0.9.25).
+* Added GitHub actions CI to build, test and check formatting on Go 1.14-1.17.
+
+## v0.9.26 (2021-09-21)
+
+* Added default no-op logger to `Client` in order to prevent panic when the
+ application doesn't set a logger.
+
+## v0.9.25 (2021-09-19)
+
+* Disabled logging request JSON for sensitive requests like `/login`,
+ `/register` and other UIA endpoints. Logging can still be enabled by
+ setting `MAUTRIX_LOG_SENSITIVE_CONTENT` to `yes`.
+* Added option to store new homeserver URL from `/login` response well-known data.
+* Added option to stream big sync responses via disk to maybe reduce memory usage.
+* Fixed trailing slashes in homeserver URL breaking all requests.
+
+## v0.9.24 (2021-09-03)
+
+* Added write deadline for appservice websocket connection.
+
+## v0.9.23 (2021-08-31)
+
+* Fixed storing e2ee key withheld events in the SQL store.
+
+## v0.9.22 (2021-08-30)
+
+* Updated appservice handler to cache multiple recent transaction IDs
+ instead of only the most recent one.
+
+## v0.9.21 (2021-08-25)
+
+* Added liveness and readiness endpoints to appservices.
+ * The endpoints are the same as mautrix-python:
+ `/_matrix/mau/live` and `/_matrix/mau/ready`
+ * Liveness always returns 200 and an empty JSON object by default,
+ but it can be turned off by setting `appservice.Live` to `false`.
+ * Readiness defaults to returning 500, and it can be switched to 200
+ by setting `appservice.Ready` to `true`.
+
+## v0.9.20 (2021-08-19)
+
+* Added crypto store migration for converting all `VARCHAR(255)` columns
+ to `TEXT` in Postgres databases.
+
+## v0.9.19 (2021-08-17)
+
+* Fixed HTML parser outputting two newlines after paragraph tags.
+
+## v0.9.18 (2021-08-16)
+
+* Added new `BuildURL` method that does the same as `Client.BuildBaseURL`
+ but without requiring the `Client` instance.
+
+## v0.9.17 (2021-07-25)
+
+* Fixed handling OTK counts and device lists coming in through the appservice
+ transaction websocket.
+* Updated OlmMachine to ignore OTK counts intended for other devices.
+
+## v0.9.15 (2021-07-16)
+
+* Added support for [MSC3202] and the to-device part of [MSC2409] in the
+ appservice package.
+* Added support for sending commands through appservice websocket.
+* Changed error message JSON field name in appservice error responses to
+ conform with standard Matrix errors (`message` -> `error`).
+
+[MSC3202]: https://github.com/matrix-org/matrix-spec-proposals/pull/3202
+
+## v0.9.14 (2021-06-17)
+
+* Added default implementation of `PillConverter` in HTML parser utility.
+
+## v0.9.13 (2021-06-15)
+
+* Added support for parsing and generating encoded matrix.to URLs and `matrix:` URIs ([MSC2312](https://github.com/matrix-org/matrix-doc/pull/2312)).
+* Updated HTML parser to use new URI parser for parsing user/room pills.
+
+## v0.9.12 (2021-05-18)
+
+* Added new method for sending custom data with read receipts
+ (not currently a part of the spec).
+
+## v0.9.11 (2021-05-12)
+
+* Improved debug log for unsupported event types.
+* Added VoIP events to GuessClass.
+* Added support for parsing strings in VoIP event version field.
+
+## v0.9.10 (2021-04-29)
+
+* Fixed `format.RenderMarkdown()` still allowing HTML when both `allowHTML`
+ and `allowMarkdown` are `false`.
+
+## v0.9.9 (2021-04-26)
+
+* Updated appservice `StartWebsocket` to return websocket close info.
+
+## v0.9.8 (2021-04-20)
+
+* Added methods for getting room tags and account data.
+
+## v0.9.7 (2021-04-19)
+
+* **Breaking change (crypto):** `SendEncryptedToDevice` now requires an event
+ type parameter. Previously it only allowed sending events of type
+ `event.ToDeviceForwardedRoomKey`.
+* Added content structs for VoIP events.
+* Added global mutex for Olm decryption
+ (previously it was only used for encryption).
+
+## v0.9.6 (2021-04-15)
+
+* Added option to retry all HTTP requests when encountering a HTTP network
+ error or gateway error response (502/503/504)
+ * Disabled by default, you need to set the `DefaultHTTPRetries` field in
+ the `AppService` or `Client` struct to enable.
+ * Can also be enabled with `FullRequest`s `MaxAttempts` field.
+
+## v0.9.5 (2021-04-06)
+
+* Reverted update of `golang.org/x/sys` which broke Go 1.14 / darwin/arm.
+
+## v0.9.4 (2021-04-06)
+
+* Switched appservices to using shared `http.Client` instance with a in-memory
+ cookie jar.
+
+## v0.9.3 (2021-03-26)
+
+* Made user agent headers easier to configure.
+* Improved logging when receiving weird/unhandled to-device events.
+
+## v0.9.2 (2021-03-15)
+
+* Fixed type of presence state constants (thanks to [@babolivier] in [#30]).
+* Implemented presence state fetching methods (thanks to [@babolivier] in [#29]).
+* Added support for sending and receiving commands via appservice transaction websocket.
+
+[@babolivier]: https://github.com/babolivier
+[#29]: https://github.com/mautrix/go/pull/29
+[#30]: https://github.com/mautrix/go/pull/30
+
+## v0.9.1 (2021-03-11)
+
+* Fixed appservice register request hiding actual errors due to UIA error handling.
+
+## v0.9.0 (2021-03-04)
+
+* **Breaking change (manual API requests):** `MakeFullRequest` now takes a
+ `FullRequest` struct instead of individual parameters. `MakeRequest`'s
+ parameters are unchanged.
+* **Breaking change (manual /sync):** `SyncRequest` now requires a `Context`
+ parameter.
+* **Breaking change (end-to-bridge encryption):**
+ the `uk.half-shot.msc2778.login.application_service` constant used for
+ appservice login ([MSC2778]) was renamed from `AuthTypeAppservice`
+ to `AuthTypeHalfyAppservice`.
+ * The `AuthTypeAppservice` constant now contains `m.login.application_service`,
+ which is currently only used for registrations, but will also be used for
+ login once MSC2778 lands in the spec.
+* Fixed appservice registration requests to include `m.login.application_service`
+ as the `type` (re [matrix-org/synapse#9548]).
+* Added wrapper for `/logout/all`.
+
+[MSC2778]: https://github.com/matrix-org/matrix-spec-proposals/pull/2778
+[matrix-org/synapse#9548]: https://github.com/matrix-org/synapse/pull/9548
+
+## v0.8.6 (2021-03-02)
+
+* Added client-side timeout to `mautrix.Client`'s `http.Client`
+ (defaults to 3 minutes).
+* Updated maulogger to fix bug where plaintext file logs wouldn't have newlines.
+
+## v0.8.5 (2021-02-26)
+
+* Fixed potential concurrent map writes in appservice `Client` and `Intent`
+ methods.
+
+## v0.8.4 (2021-02-24)
+
+* Added option to output appservice logs as JSON.
+* Added new methods for validating user ID localparts.
+
+## v0.8.3 (2021-02-21)
+
+* Allowed empty content URIs in parser
+* Added functions for device management endpoints
+ (thanks to [@edwargix] in [#26]).
+
+[@edwargix]: https://github.com/edwargix
+[#26]: https://github.com/mautrix/go/pull/26
+
+## v0.8.2 (2021-02-09)
+
+* Fixed error when removing the user's avatar.
+
+## v0.8.1 (2021-02-09)
+
+* Added AccountDataStore to remove the need for persistent local storage other
+ than the access token (thanks to [@daenney] in [#23]).
+* Added support for receiving appservice transactions over websocket.
+ See <https://github.com/mautrix/wsproxy> for the server-side implementation.
+* Fixed error when removing the room avatar.
+
+[@daenney]: https://github.com/daenney
+[#23]: https://github.com/mautrix/go/pull/23
+
+## v0.8.0 (2020-12-24)
+
+* **Breaking change:** the `RateLimited` field in the `Registration` struct is
+ now a pointer, so that it can be omitted entirely.
+* Merged initial SSSS/cross-signing code by [@nikofil]. Interactive verification
+ doesn't work, but the other things mostly do.
+* Added support for authorization header auth in appservices ([MSC2832]).
+* Added support for receiving ephemeral events directly ([MSC2409]).
+* Fixed `SendReaction()` and other similar methods in the `Client` struct.
+* Fixed crypto cgo code panicking in Go 1.15.3+.
+* Fixed olm session locks sometime getting deadlocked.
+
+[MSC2832]: https://github.com/matrix-org/matrix-spec-proposals/pull/2832
+[MSC2409]: https://github.com/matrix-org/matrix-spec-proposals/pull/2409
+[@nikofil]: https://github.com/nikofil
diff --git a/vendor/maunium.net/go/mautrix/LICENSE b/vendor/maunium.net/go/mautrix/LICENSE
new file mode 100644
index 00000000..a612ad98
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/LICENSE
@@ -0,0 +1,373 @@
+Mozilla Public License Version 2.0
+==================================
+
+1. Definitions
+--------------
+
+1.1. "Contributor"
+ means each individual or legal entity that creates, contributes to
+ the creation of, or owns Covered Software.
+
+1.2. "Contributor Version"
+ means the combination of the Contributions of others (if any) used
+ by a Contributor and that particular Contributor's Contribution.
+
+1.3. "Contribution"
+ means Covered Software of a particular Contributor.
+
+1.4. "Covered Software"
+ means Source Code Form to which the initial Contributor has attached
+ the notice in Exhibit A, the Executable Form of such Source Code
+ Form, and Modifications of such Source Code Form, in each case
+ including portions thereof.
+
+1.5. "Incompatible With Secondary Licenses"
+ means
+
+ (a) that the initial Contributor has attached the notice described
+ in Exhibit B to the Covered Software; or
+
+ (b) that the Covered Software was made available under the terms of
+ version 1.1 or earlier of the License, but not also under the
+ terms of a Secondary License.
+
+1.6. "Executable Form"
+ means any form of the work other than Source Code Form.
+
+1.7. "Larger Work"
+ means a work that combines Covered Software with other material, in
+ a separate file or files, that is not Covered Software.
+
+1.8. "License"
+ means this document.
+
+1.9. "Licensable"
+ means having the right to grant, to the maximum extent possible,
+ whether at the time of the initial grant or subsequently, any and
+ all of the rights conveyed by this License.
+
+1.10. "Modifications"
+ means any of the following:
+
+ (a) any file in Source Code Form that results from an addition to,
+ deletion from, or modification of the contents of Covered
+ Software; or
+
+ (b) any new file in Source Code Form that contains any Covered
+ Software.
+
+1.11. "Patent Claims" of a Contributor
+ means any patent claim(s), including without limitation, method,
+ process, and apparatus claims, in any patent Licensable by such
+ Contributor that would be infringed, but for the grant of the
+ License, by the making, using, selling, offering for sale, having
+ made, import, or transfer of either its Contributions or its
+ Contributor Version.
+
+1.12. "Secondary License"
+ means either the GNU General Public License, Version 2.0, the GNU
+ Lesser General Public License, Version 2.1, the GNU Affero General
+ Public License, Version 3.0, or any later versions of those
+ licenses.
+
+1.13. "Source Code Form"
+ means the form of the work preferred for making modifications.
+
+1.14. "You" (or "Your")
+ means an individual or a legal entity exercising rights under this
+ License. For legal entities, "You" includes any entity that
+ controls, is controlled by, or is under common control with You. For
+ purposes of this definition, "control" means (a) the power, direct
+ or indirect, to cause the direction or management of such entity,
+ whether by contract or otherwise, or (b) ownership of more than
+ fifty percent (50%) of the outstanding shares or beneficial
+ ownership of such entity.
+
+2. License Grants and Conditions
+--------------------------------
+
+2.1. Grants
+
+Each Contributor hereby grants You a world-wide, royalty-free,
+non-exclusive license:
+
+(a) under intellectual property rights (other than patent or trademark)
+ Licensable by such Contributor to use, reproduce, make available,
+ modify, display, perform, distribute, and otherwise exploit its
+ Contributions, either on an unmodified basis, with Modifications, or
+ as part of a Larger Work; and
+
+(b) under Patent Claims of such Contributor to make, use, sell, offer
+ for sale, have made, import, and otherwise transfer either its
+ Contributions or its Contributor Version.
+
+2.2. Effective Date
+
+The licenses granted in Section 2.1 with respect to any Contribution
+become effective for each Contribution on the date the Contributor first
+distributes such Contribution.
+
+2.3. Limitations on Grant Scope
+
+The licenses granted in this Section 2 are the only rights granted under
+this License. No additional rights or licenses will be implied from the
+distribution or licensing of Covered Software under this License.
+Notwithstanding Section 2.1(b) above, no patent license is granted by a
+Contributor:
+
+(a) for any code that a Contributor has removed from Covered Software;
+ or
+
+(b) for infringements caused by: (i) Your and any other third party's
+ modifications of Covered Software, or (ii) the combination of its
+ Contributions with other software (except as part of its Contributor
+ Version); or
+
+(c) under Patent Claims infringed by Covered Software in the absence of
+ its Contributions.
+
+This License does not grant any rights in the trademarks, service marks,
+or logos of any Contributor (except as may be necessary to comply with
+the notice requirements in Section 3.4).
+
+2.4. Subsequent Licenses
+
+No Contributor makes additional grants as a result of Your choice to
+distribute the Covered Software under a subsequent version of this
+License (see Section 10.2) or under the terms of a Secondary License (if
+permitted under the terms of Section 3.3).
+
+2.5. Representation
+
+Each Contributor represents that the Contributor believes its
+Contributions are its original creation(s) or it has sufficient rights
+to grant the rights to its Contributions conveyed by this License.
+
+2.6. Fair Use
+
+This License is not intended to limit any rights You have under
+applicable copyright doctrines of fair use, fair dealing, or other
+equivalents.
+
+2.7. Conditions
+
+Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
+in Section 2.1.
+
+3. Responsibilities
+-------------------
+
+3.1. Distribution of Source Form
+
+All distribution of Covered Software in Source Code Form, including any
+Modifications that You create or to which You contribute, must be under
+the terms of this License. You must inform recipients that the Source
+Code Form of the Covered Software is governed by the terms of this
+License, and how they can obtain a copy of this License. You may not
+attempt to alter or restrict the recipients' rights in the Source Code
+Form.
+
+3.2. Distribution of Executable Form
+
+If You distribute Covered Software in Executable Form then:
+
+(a) such Covered Software must also be made available in Source Code
+ Form, as described in Section 3.1, and You must inform recipients of
+ the Executable Form how they can obtain a copy of such Source Code
+ Form by reasonable means in a timely manner, at a charge no more
+ than the cost of distribution to the recipient; and
+
+(b) You may distribute such Executable Form under the terms of this
+ License, or sublicense it under different terms, provided that the
+ license for the Executable Form does not attempt to limit or alter
+ the recipients' rights in the Source Code Form under this License.
+
+3.3. Distribution of a Larger Work
+
+You may create and distribute a Larger Work under terms of Your choice,
+provided that You also comply with the requirements of this License for
+the Covered Software. If the Larger Work is a combination of Covered
+Software with a work governed by one or more Secondary Licenses, and the
+Covered Software is not Incompatible With Secondary Licenses, this
+License permits You to additionally distribute such Covered Software
+under the terms of such Secondary License(s), so that the recipient of
+the Larger Work may, at their option, further distribute the Covered
+Software under the terms of either this License or such Secondary
+License(s).
+
+3.4. Notices
+
+You may not remove or alter the substance of any license notices
+(including copyright notices, patent notices, disclaimers of warranty,
+or limitations of liability) contained within the Source Code Form of
+the Covered Software, except that You may alter any license notices to
+the extent required to remedy known factual inaccuracies.
+
+3.5. Application of Additional Terms
+
+You may choose to offer, and to charge a fee for, warranty, support,
+indemnity or liability obligations to one or more recipients of Covered
+Software. However, You may do so only on Your own behalf, and not on
+behalf of any Contributor. You must make it absolutely clear that any
+such warranty, support, indemnity, or liability obligation is offered by
+You alone, and You hereby agree to indemnify every Contributor for any
+liability incurred by such Contributor as a result of warranty, support,
+indemnity or liability terms You offer. You may include additional
+disclaimers of warranty and limitations of liability specific to any
+jurisdiction.
+
+4. Inability to Comply Due to Statute or Regulation
+---------------------------------------------------
+
+If it is impossible for You to comply with any of the terms of this
+License with respect to some or all of the Covered Software due to
+statute, judicial order, or regulation then You must: (a) comply with
+the terms of this License to the maximum extent possible; and (b)
+describe the limitations and the code they affect. Such description must
+be placed in a text file included with all distributions of the Covered
+Software under this License. Except to the extent prohibited by statute
+or regulation, such description must be sufficiently detailed for a
+recipient of ordinary skill to be able to understand it.
+
+5. Termination
+--------------
+
+5.1. The rights granted under this License will terminate automatically
+if You fail to comply with any of its terms. However, if You become
+compliant, then the rights granted under this License from a particular
+Contributor are reinstated (a) provisionally, unless and until such
+Contributor explicitly and finally terminates Your grants, and (b) on an
+ongoing basis, if such Contributor fails to notify You of the
+non-compliance by some reasonable means prior to 60 days after You have
+come back into compliance. Moreover, Your grants from a particular
+Contributor are reinstated on an ongoing basis if such Contributor
+notifies You of the non-compliance by some reasonable means, this is the
+first time You have received notice of non-compliance with this License
+from such Contributor, and You become compliant prior to 30 days after
+Your receipt of the notice.
+
+5.2. If You initiate litigation against any entity by asserting a patent
+infringement claim (excluding declaratory judgment actions,
+counter-claims, and cross-claims) alleging that a Contributor Version
+directly or indirectly infringes any patent, then the rights granted to
+You by any and all Contributors for the Covered Software under Section
+2.1 of this License shall terminate.
+
+5.3. In the event of termination under Sections 5.1 or 5.2 above, all
+end user license agreements (excluding distributors and resellers) which
+have been validly granted by You or Your distributors under this License
+prior to termination shall survive termination.
+
+************************************************************************
+* *
+* 6. Disclaimer of Warranty *
+* ------------------------- *
+* *
+* Covered Software is provided under this License on an "as is" *
+* basis, without warranty of any kind, either expressed, implied, or *
+* statutory, including, without limitation, warranties that the *
+* Covered Software is free of defects, merchantable, fit for a *
+* particular purpose or non-infringing. The entire risk as to the *
+* quality and performance of the Covered Software is with You. *
+* Should any Covered Software prove defective in any respect, You *
+* (not any Contributor) assume the cost of any necessary servicing, *
+* repair, or correction. This disclaimer of warranty constitutes an *
+* essential part of this License. No use of any Covered Software is *
+* authorized under this License except under this disclaimer. *
+* *
+************************************************************************
+
+************************************************************************
+* *
+* 7. Limitation of Liability *
+* -------------------------- *
+* *
+* Under no circumstances and under no legal theory, whether tort *
+* (including negligence), contract, or otherwise, shall any *
+* Contributor, or anyone who distributes Covered Software as *
+* permitted above, be liable to You for any direct, indirect, *
+* special, incidental, or consequential damages of any character *
+* including, without limitation, damages for lost profits, loss of *
+* goodwill, work stoppage, computer failure or malfunction, or any *
+* and all other commercial damages or losses, even if such party *
+* shall have been informed of the possibility of such damages. This *
+* limitation of liability shall not apply to liability for death or *
+* personal injury resulting from such party's negligence to the *
+* extent applicable law prohibits such limitation. Some *
+* jurisdictions do not allow the exclusion or limitation of *
+* incidental or consequential damages, so this exclusion and *
+* limitation may not apply to You. *
+* *
+************************************************************************
+
+8. Litigation
+-------------
+
+Any litigation relating to this License may be brought only in the
+courts of a jurisdiction where the defendant maintains its principal
+place of business and such litigation shall be governed by laws of that
+jurisdiction, without reference to its conflict-of-law provisions.
+Nothing in this Section shall prevent a party's ability to bring
+cross-claims or counter-claims.
+
+9. Miscellaneous
+----------------
+
+This License represents the complete agreement concerning the subject
+matter hereof. If any provision of this License is held to be
+unenforceable, such provision shall be reformed only to the extent
+necessary to make it enforceable. Any law or regulation which provides
+that the language of a contract shall be construed against the drafter
+shall not be used to construe this License against a Contributor.
+
+10. Versions of the License
+---------------------------
+
+10.1. New Versions
+
+Mozilla Foundation is the license steward. Except as provided in Section
+10.3, no one other than the license steward has the right to modify or
+publish new versions of this License. Each version will be given a
+distinguishing version number.
+
+10.2. Effect of New Versions
+
+You may distribute the Covered Software under the terms of the version
+of the License under which You originally received the Covered Software,
+or under the terms of any subsequent version published by the license
+steward.
+
+10.3. Modified Versions
+
+If you create software not governed by this License, and you want to
+create a new license for such software, you may create and use a
+modified version of this License if you rename the license and remove
+any references to the name of the license steward (except to note that
+such modified license differs from this License).
+
+10.4. Distributing Source Code Form that is Incompatible With Secondary
+Licenses
+
+If You choose to distribute Source Code Form that is Incompatible With
+Secondary Licenses under the terms of this version of the License, the
+notice described in Exhibit B of this License must be attached.
+
+Exhibit A - Source Code Form License Notice
+-------------------------------------------
+
+ This Source Code Form is subject to the terms of the Mozilla Public
+ License, v. 2.0. If a copy of the MPL was not distributed with this
+ file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+If it is not possible or desirable to put the notice in a particular
+file, then You may include the notice in a location (such as a LICENSE
+file in a relevant directory) where a recipient would be likely to look
+for such a notice.
+
+You may add additional accurate notices of copyright ownership.
+
+Exhibit B - "Incompatible With Secondary Licenses" Notice
+---------------------------------------------------------
+
+ This Source Code Form is "Incompatible With Secondary Licenses", as
+ defined by the Mozilla Public License, v. 2.0.
diff --git a/vendor/maunium.net/go/mautrix/README.md b/vendor/maunium.net/go/mautrix/README.md
new file mode 100644
index 00000000..04fdc0e9
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/README.md
@@ -0,0 +1,24 @@
+# mautrix-go
+[![GoDoc](https://pkg.go.dev/badge/maunium.net/go/mautrix)](https://pkg.go.dev/maunium.net/go/mautrix)
+
+A Golang Matrix framework. Used by [gomuks](https://matrix.org/docs/projects/client/gomuks),
+[go-neb](https://github.com/matrix-org/go-neb), [mautrix-whatsapp](https://github.com/mautrix/whatsapp)
+and others.
+
+Matrix room: [`#maunium:maunium.net`](https://matrix.to/#/#maunium:maunium.net)
+
+This project is based on [matrix-org/gomatrix](https://github.com/matrix-org/gomatrix).
+The original project is licensed under [Apache 2.0](https://github.com/matrix-org/gomatrix/blob/master/LICENSE).
+
+In addition to the basic client API features the original project has, this framework also has:
+
+* Appservice support (Intent API like mautrix-python, room state storage, etc)
+* End-to-end encryption support (incl. interactive SAS verification)
+* Structs for parsing event content
+* Helpers for parsing and generating Matrix HTML
+* Helpers for handling push rules
+
+This project contains modules that are licensed under Apache 2.0:
+
+* [maunium.net/go/mautrix/crypto/canonicaljson](crypto/canonicaljson)
+* [maunium.net/go/mautrix/crypto/olm](crypto/olm)
diff --git a/vendor/maunium.net/go/mautrix/appservice/appservice.go b/vendor/maunium.net/go/mautrix/appservice/appservice.go
new file mode 100644
index 00000000..099e4b27
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/appservice/appservice.go
@@ -0,0 +1,350 @@
+// Copyright (c) 2023 Tulir Asokan
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package appservice
+
+import (
+ "context"
+ "fmt"
+ "net"
+ "net/http"
+ "net/http/cookiejar"
+ "net/url"
+ "os"
+ "strings"
+ "sync"
+ "syscall"
+ "time"
+
+ "github.com/gorilla/mux"
+ "github.com/gorilla/websocket"
+ "github.com/rs/zerolog"
+ "golang.org/x/net/publicsuffix"
+ "gopkg.in/yaml.v3"
+ "maunium.net/go/maulogger/v2/maulogadapt"
+
+ "maunium.net/go/mautrix"
+ "maunium.net/go/mautrix/event"
+ "maunium.net/go/mautrix/id"
+)
+
+// EventChannelSize is the size for the Events channel in Appservice instances.
+var EventChannelSize = 64
+var OTKChannelSize = 4
+
+// Create a blank appservice instance.
+func Create() *AppService {
+ jar, _ := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List})
+ as := &AppService{
+ Log: zerolog.Nop(),
+ clients: make(map[id.UserID]*mautrix.Client),
+ intents: make(map[id.UserID]*IntentAPI),
+ HTTPClient: &http.Client{Timeout: 180 * time.Second, Jar: jar},
+ StateStore: mautrix.NewMemoryStateStore().(StateStore),
+ Router: mux.NewRouter(),
+ UserAgent: mautrix.DefaultUserAgent,
+ txnIDC: NewTransactionIDCache(128),
+ Live: true,
+ Ready: false,
+ ProcessID: getDefaultProcessID(),
+
+ Events: make(chan *event.Event, EventChannelSize),
+ ToDeviceEvents: make(chan *event.Event, EventChannelSize),
+ OTKCounts: make(chan *mautrix.OTKCount, OTKChannelSize),
+ DeviceLists: make(chan *mautrix.DeviceLists, EventChannelSize),
+ QueryHandler: &QueryHandlerStub{},
+ }
+
+ as.Router.HandleFunc("/transactions/{txnID}", as.PutTransaction).Methods(http.MethodPut)
+ as.Router.HandleFunc("/rooms/{roomAlias}", as.GetRoom).Methods(http.MethodGet)
+ as.Router.HandleFunc("/users/{userID}", as.GetUser).Methods(http.MethodGet)
+ as.Router.HandleFunc("/_matrix/app/v1/transactions/{txnID}", as.PutTransaction).Methods(http.MethodPut)
+ as.Router.HandleFunc("/_matrix/app/v1/rooms/{roomAlias}", as.GetRoom).Methods(http.MethodGet)
+ as.Router.HandleFunc("/_matrix/app/v1/users/{userID}", as.GetUser).Methods(http.MethodGet)
+ as.Router.HandleFunc("/_matrix/app/v1/ping", as.PostPing).Methods(http.MethodPost)
+ as.Router.HandleFunc("/_matrix/app/unstable/fi.mau.msc2659/ping", as.PostPing).Methods(http.MethodPost)
+ as.Router.HandleFunc("/_matrix/mau/live", as.GetLive).Methods(http.MethodGet)
+ as.Router.HandleFunc("/_matrix/mau/ready", as.GetReady).Methods(http.MethodGet)
+
+ return as
+}
+
+// QueryHandler handles room alias and user ID queries from the homeserver.
+type QueryHandler interface {
+ QueryAlias(alias string) bool
+ QueryUser(userID id.UserID) bool
+}
+
+type QueryHandlerStub struct{}
+
+func (qh *QueryHandlerStub) QueryAlias(alias string) bool {
+ return false
+}
+
+func (qh *QueryHandlerStub) QueryUser(userID id.UserID) bool {
+ return false
+}
+
+type WebsocketHandler func(WebsocketCommand) (ok bool, data interface{})
+
+type StateStore interface {
+ mautrix.StateStore
+
+ IsRegistered(userID id.UserID) bool
+ MarkRegistered(userID id.UserID)
+
+ GetPowerLevel(roomID id.RoomID, userID id.UserID) int
+ GetPowerLevelRequirement(roomID id.RoomID, eventType event.Type) int
+ HasPowerLevel(roomID id.RoomID, userID id.UserID, eventType event.Type) bool
+}
+
+// AppService is the main config for all appservices.
+// It also serves as the appservice instance struct.
+type AppService struct {
+ HomeserverDomain string
+ hsURLForClient *url.URL
+ Host HostConfig
+
+ Registration *Registration
+ Log zerolog.Logger
+
+ txnIDC *TransactionIDCache
+
+ Events chan *event.Event
+ ToDeviceEvents chan *event.Event
+ DeviceLists chan *mautrix.DeviceLists
+ OTKCounts chan *mautrix.OTKCount
+ QueryHandler QueryHandler
+ StateStore StateStore
+
+ Router *mux.Router
+ UserAgent string
+ server *http.Server
+ HTTPClient *http.Client
+ botClient *mautrix.Client
+ botIntent *IntentAPI
+
+ DefaultHTTPRetries int
+
+ Live bool
+ Ready bool
+
+ clients map[id.UserID]*mautrix.Client
+ clientsLock sync.RWMutex
+ intents map[id.UserID]*IntentAPI
+ intentsLock sync.RWMutex
+
+ ws *websocket.Conn
+ wsWriteLock sync.Mutex
+ StopWebsocket func(error)
+ websocketHandlers map[string]WebsocketHandler
+ websocketHandlersLock sync.RWMutex
+ websocketRequests map[int]chan<- *WebsocketCommand
+ websocketRequestsLock sync.RWMutex
+ websocketRequestID int32
+ // ProcessID is an identifier sent to the websocket proxy for debugging connections
+ ProcessID string
+
+ DoublePuppetValue string
+ GetProfile func(userID id.UserID, roomID id.RoomID) *event.MemberEventContent
+}
+
+const DoublePuppetKey = "fi.mau.double_puppet_source"
+
+func getDefaultProcessID() string {
+ pid := syscall.Getpid()
+ uid := syscall.Getuid()
+ hostname, _ := os.Hostname()
+ return fmt.Sprintf("%s-%d-%d", hostname, uid, pid)
+}
+
+func (as *AppService) PrepareWebsocket() {
+ as.websocketHandlersLock.Lock()
+ defer as.websocketHandlersLock.Unlock()
+ if as.websocketHandlers == nil {
+ as.websocketHandlers = make(map[string]WebsocketHandler, 32)
+ as.websocketRequests = make(map[int]chan<- *WebsocketCommand)
+ }
+}
+
+// HostConfig contains info about how to host the appservice.
+type HostConfig struct {
+ Hostname string `yaml:"hostname"`
+ Port uint16 `yaml:"port"`
+ TLSKey string `yaml:"tls_key,omitempty"`
+ TLSCert string `yaml:"tls_cert,omitempty"`
+}
+
+// Address gets the whole address of the Appservice.
+func (hc *HostConfig) Address() string {
+ return fmt.Sprintf("%s:%d", hc.Hostname, hc.Port)
+}
+
+func (hc *HostConfig) IsUnixSocket() bool {
+ return strings.HasPrefix(hc.Hostname, "/")
+}
+
+func (hc *HostConfig) IsConfigured() bool {
+ return hc.IsUnixSocket() || hc.Port != 0
+}
+
+// Save saves this config into a file at the given path.
+func (as *AppService) Save(path string) error {
+ data, err := yaml.Marshal(as)
+ if err != nil {
+ return err
+ }
+ return os.WriteFile(path, data, 0644)
+}
+
+// YAML returns the config in YAML format.
+func (as *AppService) YAML() (string, error) {
+ data, err := yaml.Marshal(as)
+ if err != nil {
+ return "", err
+ }
+ return string(data), nil
+}
+
+func (as *AppService) BotMXID() id.UserID {
+ return id.NewUserID(as.Registration.SenderLocalpart, as.HomeserverDomain)
+}
+
+func (as *AppService) makeIntent(userID id.UserID) *IntentAPI {
+ as.intentsLock.Lock()
+ defer as.intentsLock.Unlock()
+
+ intent, ok := as.intents[userID]
+ if ok {
+ return intent
+ }
+
+ localpart, homeserver, err := userID.Parse()
+ if err != nil || len(localpart) == 0 || homeserver != as.HomeserverDomain {
+ if err != nil {
+ as.Log.Error().Err(err).
+ Str("user_id", userID.String()).
+ Msg("Failed to parse user ID")
+ } else if len(localpart) == 0 {
+ as.Log.Error().Err(err).
+ Str("user_id", userID.String()).
+ Msg("Failed to make intent: localpart is empty")
+ } else if homeserver != as.HomeserverDomain {
+ as.Log.Error().Err(err).
+ Str("user_id", userID.String()).
+ Str("expected_homeserver", as.HomeserverDomain).
+ Msg("Failed to make intent: homeserver doesn't match")
+ }
+ return nil
+ }
+ intent = as.NewIntentAPI(localpart)
+ as.intents[userID] = intent
+ return intent
+}
+
+func (as *AppService) Intent(userID id.UserID) *IntentAPI {
+ as.intentsLock.RLock()
+ intent, ok := as.intents[userID]
+ as.intentsLock.RUnlock()
+ if !ok {
+ return as.makeIntent(userID)
+ }
+ return intent
+}
+
+func (as *AppService) BotIntent() *IntentAPI {
+ if as.botIntent == nil {
+ as.botIntent = as.makeIntent(as.BotMXID())
+ }
+ return as.botIntent
+}
+
+func (as *AppService) SetHomeserverURL(homeserverURL string) error {
+ parsedURL, err := url.Parse(homeserverURL)
+ if err != nil {
+ return err
+ }
+
+ as.hsURLForClient = parsedURL
+ if as.hsURLForClient.Scheme == "unix" {
+ as.hsURLForClient.Scheme = "http"
+ as.hsURLForClient.Host = "unix"
+ as.hsURLForClient.Path = ""
+ } else if as.hsURLForClient.Scheme == "" {
+ as.hsURLForClient.Scheme = "https"
+ }
+ as.hsURLForClient.RawPath = parsedURL.EscapedPath()
+
+ jar, _ := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List})
+ as.HTTPClient = &http.Client{Timeout: 180 * time.Second, Jar: jar}
+ if parsedURL.Scheme == "unix" {
+ as.HTTPClient.Transport = &http.Transport{
+ DialContext: func(_ context.Context, _, _ string) (net.Conn, error) {
+ return net.Dial("unix", parsedURL.Path)
+ },
+ }
+ }
+ return nil
+}
+
+func (as *AppService) NewMautrixClient(userID id.UserID) *mautrix.Client {
+ client := &mautrix.Client{
+ HomeserverURL: as.hsURLForClient,
+ UserID: userID,
+ SetAppServiceUserID: true,
+ AccessToken: as.Registration.AppToken,
+ UserAgent: as.UserAgent,
+ StateStore: as.StateStore,
+ Log: as.Log.With().Str("as_user_id", userID.String()).Logger(),
+ Client: as.HTTPClient,
+ DefaultHTTPRetries: as.DefaultHTTPRetries,
+ }
+ client.Logger = maulogadapt.ZeroAsMau(&client.Log)
+ return client
+}
+
+func (as *AppService) NewExternalMautrixClient(userID id.UserID, token string, homeserverURL string) (*mautrix.Client, error) {
+ client := as.NewMautrixClient(userID)
+ client.AccessToken = token
+ if homeserverURL != "" {
+ client.Client = &http.Client{Timeout: 180 * time.Second}
+ var err error
+ client.HomeserverURL, err = mautrix.ParseAndNormalizeBaseURL(homeserverURL)
+ if err != nil {
+ return nil, err
+ }
+ }
+ return client, nil
+}
+
+func (as *AppService) makeClient(userID id.UserID) *mautrix.Client {
+ as.clientsLock.Lock()
+ defer as.clientsLock.Unlock()
+
+ client, ok := as.clients[userID]
+ if !ok {
+ client = as.NewMautrixClient(userID)
+ as.clients[userID] = client
+ }
+ return client
+}
+
+func (as *AppService) Client(userID id.UserID) *mautrix.Client {
+ as.clientsLock.RLock()
+ client, ok := as.clients[userID]
+ as.clientsLock.RUnlock()
+ if !ok {
+ return as.makeClient(userID)
+ }
+ return client
+}
+
+func (as *AppService) BotClient() *mautrix.Client {
+ if as.botClient == nil {
+ as.botClient = as.makeClient(as.BotMXID())
+ }
+ return as.botClient
+}
diff --git a/vendor/maunium.net/go/mautrix/appservice/eventprocessor.go b/vendor/maunium.net/go/mautrix/appservice/eventprocessor.go
new file mode 100644
index 00000000..437d8536
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/appservice/eventprocessor.go
@@ -0,0 +1,175 @@
+// Copyright (c) 2023 Tulir Asokan
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package appservice
+
+import (
+ "encoding/json"
+ "runtime/debug"
+
+ "github.com/rs/zerolog"
+
+ "maunium.net/go/mautrix"
+ "maunium.net/go/mautrix/event"
+)
+
+type ExecMode uint8
+
+const (
+ AsyncHandlers ExecMode = iota
+ AsyncLoop
+ Sync
+)
+
+type EventHandler = func(evt *event.Event)
+type OTKHandler = func(otk *mautrix.OTKCount)
+type DeviceListHandler = func(lists *mautrix.DeviceLists, since string)
+
+type EventProcessor struct {
+ ExecMode ExecMode
+
+ as *AppService
+ stop chan struct{}
+ handlers map[event.Type][]EventHandler
+
+ otkHandlers []OTKHandler
+ deviceListHandlers []DeviceListHandler
+}
+
+func NewEventProcessor(as *AppService) *EventProcessor {
+ return &EventProcessor{
+ ExecMode: AsyncHandlers,
+ as: as,
+ stop: make(chan struct{}, 1),
+ handlers: make(map[event.Type][]EventHandler),
+
+ otkHandlers: make([]OTKHandler, 0),
+ deviceListHandlers: make([]DeviceListHandler, 0),
+ }
+}
+
+func (ep *EventProcessor) On(evtType event.Type, handler EventHandler) {
+ handlers, ok := ep.handlers[evtType]
+ if !ok {
+ handlers = []EventHandler{handler}
+ } else {
+ handlers = append(handlers, handler)
+ }
+ ep.handlers[evtType] = handlers
+}
+
+func (ep *EventProcessor) PrependHandler(evtType event.Type, handler EventHandler) {
+ handlers, ok := ep.handlers[evtType]
+ if !ok {
+ handlers = []EventHandler{handler}
+ } else {
+ handlers = append([]EventHandler{handler}, handlers...)
+ }
+ ep.handlers[evtType] = handlers
+}
+
+func (ep *EventProcessor) OnOTK(handler OTKHandler) {
+ ep.otkHandlers = append(ep.otkHandlers, handler)
+}
+
+func (ep *EventProcessor) OnDeviceList(handler DeviceListHandler) {
+ ep.deviceListHandlers = append(ep.deviceListHandlers, handler)
+}
+
+func (ep *EventProcessor) recoverFunc(data interface{}) {
+ if err := recover(); err != nil {
+ d, _ := json.Marshal(data)
+ ep.as.Log.Error().
+ Str(zerolog.ErrorStackFieldName, string(debug.Stack())).
+ Interface(zerolog.ErrorFieldName, err).
+ Str("event_content", string(d)).
+ Msg("Panic in Matrix event handler")
+ }
+}
+
+func (ep *EventProcessor) callHandler(handler EventHandler, evt *event.Event) {
+ defer ep.recoverFunc(evt)
+ handler(evt)
+}
+
+func (ep *EventProcessor) callOTKHandler(handler OTKHandler, otk *mautrix.OTKCount) {
+ defer ep.recoverFunc(otk)
+ handler(otk)
+}
+
+func (ep *EventProcessor) callDeviceListHandler(handler DeviceListHandler, dl *mautrix.DeviceLists) {
+ defer ep.recoverFunc(dl)
+ handler(dl, "")
+}
+
+func (ep *EventProcessor) DispatchOTK(otk *mautrix.OTKCount) {
+ for _, handler := range ep.otkHandlers {
+ go ep.callOTKHandler(handler, otk)
+ }
+}
+
+func (ep *EventProcessor) DispatchDeviceList(dl *mautrix.DeviceLists) {
+ for _, handler := range ep.deviceListHandlers {
+ go ep.callDeviceListHandler(handler, dl)
+ }
+}
+
+func (ep *EventProcessor) Dispatch(evt *event.Event) {
+ handlers, ok := ep.handlers[evt.Type]
+ if !ok {
+ return
+ }
+ switch ep.ExecMode {
+ case AsyncHandlers:
+ for _, handler := range handlers {
+ go ep.callHandler(handler, evt)
+ }
+ case AsyncLoop:
+ go func() {
+ for _, handler := range handlers {
+ ep.callHandler(handler, evt)
+ }
+ }()
+ case Sync:
+ for _, handler := range handlers {
+ ep.callHandler(handler, evt)
+ }
+ }
+}
+func (ep *EventProcessor) startEvents() {
+ for {
+ select {
+ case evt := <-ep.as.Events:
+ ep.Dispatch(evt)
+ case <-ep.stop:
+ return
+ }
+ }
+}
+
+func (ep *EventProcessor) startEncryption() {
+ for {
+ select {
+ case evt := <-ep.as.ToDeviceEvents:
+ ep.Dispatch(evt)
+ case otk := <-ep.as.OTKCounts:
+ ep.DispatchOTK(otk)
+ case dl := <-ep.as.DeviceLists:
+ ep.DispatchDeviceList(dl)
+ case <-ep.stop:
+ return
+ }
+ }
+}
+
+func (ep *EventProcessor) Start() {
+ go ep.startEvents()
+ go ep.startEncryption()
+}
+
+func (ep *EventProcessor) Stop() {
+ close(ep.stop)
+}
diff --git a/vendor/maunium.net/go/mautrix/appservice/http.go b/vendor/maunium.net/go/mautrix/appservice/http.go
new file mode 100644
index 00000000..06ac7788
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/appservice/http.go
@@ -0,0 +1,348 @@
+// Copyright (c) 2023 Tulir Asokan
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package appservice
+
+import (
+ "context"
+ "encoding/json"
+ "errors"
+ "io"
+ "net"
+ "net/http"
+ "strings"
+ "syscall"
+ "time"
+
+ "github.com/gorilla/mux"
+ "github.com/rs/zerolog"
+
+ "maunium.net/go/mautrix"
+ "maunium.net/go/mautrix/event"
+ "maunium.net/go/mautrix/id"
+)
+
+// Start starts the HTTP server that listens for calls from the Matrix homeserver.
+func (as *AppService) Start() {
+ as.server = &http.Server{
+ Handler: as.Router,
+ }
+ var err error
+ if as.Host.IsUnixSocket() {
+ err = as.listenUnix()
+ } else {
+ as.server.Addr = as.Host.Address()
+ err = as.listenTCP()
+ }
+ if err != nil && !errors.Is(err, http.ErrServerClosed) {
+ as.Log.Error().Err(err).Msg("Error in HTTP listener")
+ } else {
+ as.Log.Debug().Msg("HTTP listener stopped")
+ }
+}
+
+func (as *AppService) listenUnix() error {
+ socket := as.Host.Hostname
+ _ = syscall.Unlink(socket)
+ defer func() {
+ _ = syscall.Unlink(socket)
+ }()
+ listener, err := net.Listen("unix", socket)
+ if err != nil {
+ return err
+ }
+ as.Log.Info().Str("socket", socket).Msg("Starting unix socket HTTP listener")
+ return as.server.Serve(listener)
+}
+
+func (as *AppService) listenTCP() error {
+ if len(as.Host.TLSCert) == 0 || len(as.Host.TLSKey) == 0 {
+ as.Log.Info().Str("address", as.server.Addr).Msg("Starting HTTP listener")
+ return as.server.ListenAndServe()
+ } else {
+ as.Log.Info().Str("address", as.server.Addr).Msg("Starting HTTP listener with TLS")
+ return as.server.ListenAndServeTLS(as.Host.TLSCert, as.Host.TLSKey)
+ }
+}
+
+func (as *AppService) Stop() {
+ if as.server == nil {
+ return
+ }
+
+ ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+ defer cancel()
+ _ = as.server.Shutdown(ctx)
+ as.server = nil
+}
+
+// CheckServerToken checks if the given request originated from the Matrix homeserver.
+func (as *AppService) CheckServerToken(w http.ResponseWriter, r *http.Request) (isValid bool) {
+ authHeader := r.Header.Get("Authorization")
+ if len(authHeader) > 0 && strings.HasPrefix(authHeader, "Bearer ") {
+ isValid = authHeader[len("Bearer "):] == as.Registration.ServerToken
+ } else {
+ queryToken := r.URL.Query().Get("access_token")
+ if len(queryToken) > 0 {
+ isValid = queryToken == as.Registration.ServerToken
+ } else {
+ Error{
+ ErrorCode: ErrUnknownToken,
+ HTTPStatus: http.StatusForbidden,
+ Message: "Missing access token",
+ }.Write(w)
+ return
+ }
+ }
+ if !isValid {
+ Error{
+ ErrorCode: ErrUnknownToken,
+ HTTPStatus: http.StatusForbidden,
+ Message: "Incorrect access token",
+ }.Write(w)
+ }
+ return
+}
+
+// PutTransaction handles a /transactions PUT call from the homeserver.
+func (as *AppService) PutTransaction(w http.ResponseWriter, r *http.Request) {
+ if !as.CheckServerToken(w, r) {
+ return
+ }
+
+ vars := mux.Vars(r)
+ txnID := vars["txnID"]
+ if len(txnID) == 0 {
+ Error{
+ ErrorCode: ErrNoTransactionID,
+ HTTPStatus: http.StatusBadRequest,
+ Message: "Missing transaction ID",
+ }.Write(w)
+ return
+ }
+ defer r.Body.Close()
+ body, err := io.ReadAll(r.Body)
+ if err != nil || len(body) == 0 {
+ Error{
+ ErrorCode: ErrNotJSON,
+ HTTPStatus: http.StatusBadRequest,
+ Message: "Missing request body",
+ }.Write(w)
+ return
+ }
+ log := as.Log.With().Str("transaction_id", txnID).Logger()
+ ctx := context.Background()
+ ctx = log.WithContext(ctx)
+ if as.txnIDC.IsProcessed(txnID) {
+ // Duplicate transaction ID: no-op
+ WriteBlankOK(w)
+ log.Debug().Msg("Ignoring duplicate transaction")
+ return
+ }
+
+ var txn Transaction
+ err = json.Unmarshal(body, &txn)
+ if err != nil {
+ log.Error().Err(err).Msg("Failed to parse transaction content")
+ Error{
+ ErrorCode: ErrBadJSON,
+ HTTPStatus: http.StatusBadRequest,
+ Message: "Failed to parse body JSON",
+ }.Write(w)
+ } else {
+ as.handleTransaction(ctx, txnID, &txn)
+ WriteBlankOK(w)
+ }
+}
+
+func (as *AppService) handleTransaction(ctx context.Context, id string, txn *Transaction) {
+ log := zerolog.Ctx(ctx)
+ log.Debug().Object("content", txn).Msg("Starting handling of transaction")
+ if as.Registration.EphemeralEvents {
+ if txn.EphemeralEvents != nil {
+ as.handleEvents(ctx, txn.EphemeralEvents, event.EphemeralEventType)
+ } else if txn.MSC2409EphemeralEvents != nil {
+ as.handleEvents(ctx, txn.MSC2409EphemeralEvents, event.EphemeralEventType)
+ }
+ if txn.ToDeviceEvents != nil {
+ as.handleEvents(ctx, txn.ToDeviceEvents, event.ToDeviceEventType)
+ } else if txn.MSC2409ToDeviceEvents != nil {
+ as.handleEvents(ctx, txn.MSC2409ToDeviceEvents, event.ToDeviceEventType)
+ }
+ }
+ as.handleEvents(ctx, txn.Events, event.UnknownEventType)
+ if txn.DeviceLists != nil {
+ as.handleDeviceLists(ctx, txn.DeviceLists)
+ } else if txn.MSC3202DeviceLists != nil {
+ as.handleDeviceLists(ctx, txn.MSC3202DeviceLists)
+ }
+ if txn.DeviceOTKCount != nil {
+ as.handleOTKCounts(ctx, txn.DeviceOTKCount)
+ } else if txn.MSC3202DeviceOTKCount != nil {
+ as.handleOTKCounts(ctx, txn.MSC3202DeviceOTKCount)
+ }
+ as.txnIDC.MarkProcessed(id)
+ log.Debug().Msg("Finished dispatching events from transaction")
+}
+
+func (as *AppService) handleOTKCounts(ctx context.Context, otks OTKCountMap) {
+ for userID, devices := range otks {
+ for deviceID, otkCounts := range devices {
+ otkCounts.UserID = userID
+ otkCounts.DeviceID = deviceID
+ select {
+ case as.OTKCounts <- &otkCounts:
+ default:
+ zerolog.Ctx(ctx).Warn().
+ Str("user_id", userID.String()).
+ Msg("Dropped OTK count update for user because channel is full")
+ }
+ }
+ }
+}
+
+func (as *AppService) handleDeviceLists(ctx context.Context, dl *mautrix.DeviceLists) {
+ select {
+ case as.DeviceLists <- dl:
+ default:
+ zerolog.Ctx(ctx).Warn().Msg("Dropped device list update because channel is full")
+ }
+}
+
+func (as *AppService) handleEvents(ctx context.Context, evts []*event.Event, defaultTypeClass event.TypeClass) {
+ log := zerolog.Ctx(ctx)
+ for _, evt := range evts {
+ evt.Mautrix.ReceivedAt = time.Now()
+ if defaultTypeClass != event.UnknownEventType {
+ evt.Type.Class = defaultTypeClass
+ } else if evt.StateKey != nil {
+ evt.Type.Class = event.StateEventType
+ } else {
+ evt.Type.Class = event.MessageEventType
+ }
+ err := evt.Content.ParseRaw(evt.Type)
+ if errors.Is(err, event.ErrUnsupportedContentType) {
+ log.Debug().Str("event_id", evt.ID.String()).Msg("Not parsing content of unsupported event")
+ } else if err != nil {
+ log.Warn().Err(err).
+ Str("event_id", evt.ID.String()).
+ Str("event_type", evt.Type.Type).
+ Str("event_type_class", evt.Type.Class.Name()).
+ Msg("Failed to parse content of event")
+ }
+
+ if evt.Type.IsState() {
+ // TODO remove this check after making sure the log doesn't happen
+ historical, ok := evt.Content.Raw["org.matrix.msc2716.historical"].(bool)
+ if ok && historical {
+ log.Warn().
+ Str("event_id", evt.ID.String()).
+ Str("event_type", evt.Type.Type).
+ Str("state_key", evt.GetStateKey()).
+ Msg("Received historical state event")
+ } else {
+ mautrix.UpdateStateStore(as.StateStore, evt)
+ }
+ }
+ var ch chan *event.Event
+ if evt.Type.Class == event.ToDeviceEventType {
+ ch = as.ToDeviceEvents
+ } else {
+ ch = as.Events
+ }
+ select {
+ case ch <- evt:
+ default:
+ log.Warn().
+ Str("event_id", evt.ID.String()).
+ Str("event_type", evt.Type.Type).
+ Str("event_type_class", evt.Type.Class.Name()).
+ Msg("Event channel is full")
+ ch <- evt
+ }
+ }
+}
+
+// GetRoom handles a /rooms GET call from the homeserver.
+func (as *AppService) GetRoom(w http.ResponseWriter, r *http.Request) {
+ if !as.CheckServerToken(w, r) {
+ return
+ }
+
+ vars := mux.Vars(r)
+ roomAlias := vars["roomAlias"]
+ ok := as.QueryHandler.QueryAlias(roomAlias)
+ if ok {
+ WriteBlankOK(w)
+ } else {
+ Error{
+ ErrorCode: ErrUnknown,
+ HTTPStatus: http.StatusNotFound,
+ }.Write(w)
+ }
+}
+
+// GetUser handles a /users GET call from the homeserver.
+func (as *AppService) GetUser(w http.ResponseWriter, r *http.Request) {
+ if !as.CheckServerToken(w, r) {
+ return
+ }
+
+ vars := mux.Vars(r)
+ userID := id.UserID(vars["userID"])
+ ok := as.QueryHandler.QueryUser(userID)
+ if ok {
+ WriteBlankOK(w)
+ } else {
+ Error{
+ ErrorCode: ErrUnknown,
+ HTTPStatus: http.StatusNotFound,
+ }.Write(w)
+ }
+}
+
+func (as *AppService) PostPing(w http.ResponseWriter, r *http.Request) {
+ if !as.CheckServerToken(w, r) {
+ return
+ }
+ body, err := io.ReadAll(r.Body)
+ if err != nil || len(body) == 0 || !json.Valid(body) {
+ Error{
+ ErrorCode: ErrNotJSON,
+ HTTPStatus: http.StatusBadRequest,
+ Message: "Missing request body",
+ }.Write(w)
+ return
+ }
+
+ var txn mautrix.ReqAppservicePing
+ _ = json.Unmarshal(body, &txn)
+ as.Log.Debug().Str("txn_id", txn.TxnID).Msg("Received ping from homeserver")
+
+ w.Header().Add("Content-Type", "application/json")
+ w.WriteHeader(http.StatusOK)
+ w.Write([]byte("{}"))
+}
+
+func (as *AppService) GetLive(w http.ResponseWriter, r *http.Request) {
+ w.Header().Add("Content-Type", "application/json")
+ if as.Live {
+ w.WriteHeader(http.StatusOK)
+ } else {
+ w.WriteHeader(http.StatusInternalServerError)
+ }
+ w.Write([]byte("{}"))
+}
+
+func (as *AppService) GetReady(w http.ResponseWriter, r *http.Request) {
+ w.Header().Add("Content-Type", "application/json")
+ if as.Ready {
+ w.WriteHeader(http.StatusOK)
+ } else {
+ w.WriteHeader(http.StatusInternalServerError)
+ }
+ w.Write([]byte("{}"))
+}
diff --git a/vendor/maunium.net/go/mautrix/appservice/intent.go b/vendor/maunium.net/go/mautrix/appservice/intent.go
new file mode 100644
index 00000000..af6fea37
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/appservice/intent.go
@@ -0,0 +1,419 @@
+// Copyright (c) 2020 Tulir Asokan
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package appservice
+
+import (
+ "errors"
+ "fmt"
+ "strings"
+
+ "maunium.net/go/mautrix"
+ "maunium.net/go/mautrix/event"
+ "maunium.net/go/mautrix/id"
+)
+
+type IntentAPI struct {
+ *mautrix.Client
+ bot *mautrix.Client
+ as *AppService
+ Localpart string
+ UserID id.UserID
+
+ IsCustomPuppet bool
+}
+
+func (as *AppService) NewIntentAPI(localpart string) *IntentAPI {
+ userID := id.NewUserID(localpart, as.HomeserverDomain)
+ bot := as.BotClient()
+ if userID == bot.UserID {
+ bot = nil
+ }
+ return &IntentAPI{
+ Client: as.Client(userID),
+ bot: bot,
+ as: as,
+ Localpart: localpart,
+ UserID: userID,
+
+ IsCustomPuppet: false,
+ }
+}
+
+func (intent *IntentAPI) Register() error {
+ _, _, err := intent.Client.Register(&mautrix.ReqRegister{
+ Username: intent.Localpart,
+ Type: mautrix.AuthTypeAppservice,
+ InhibitLogin: true,
+ })
+ return err
+}
+
+func (intent *IntentAPI) EnsureRegistered() error {
+ if intent.IsCustomPuppet || intent.as.StateStore.IsRegistered(intent.UserID) {
+ return nil
+ }
+
+ err := intent.Register()
+ if err != nil && !errors.Is(err, mautrix.MUserInUse) {
+ return fmt.Errorf("failed to ensure registered: %w", err)
+ }
+ intent.as.StateStore.MarkRegistered(intent.UserID)
+ return nil
+}
+
+type EnsureJoinedParams struct {
+ IgnoreCache bool
+ BotOverride *mautrix.Client
+}
+
+func (intent *IntentAPI) EnsureJoined(roomID id.RoomID, extra ...EnsureJoinedParams) error {
+ var params EnsureJoinedParams
+ if len(extra) > 1 {
+ panic("invalid number of extra parameters")
+ } else if len(extra) == 1 {
+ params = extra[0]
+ }
+ if intent.as.StateStore.IsInRoom(roomID, intent.UserID) && !params.IgnoreCache {
+ return nil
+ }
+
+ if err := intent.EnsureRegistered(); err != nil {
+ return fmt.Errorf("failed to ensure joined: %w", err)
+ }
+
+ resp, err := intent.JoinRoomByID(roomID)
+ if err != nil {
+ bot := intent.bot
+ if params.BotOverride != nil {
+ bot = params.BotOverride
+ }
+ if !errors.Is(err, mautrix.MForbidden) || bot == nil {
+ return fmt.Errorf("failed to ensure joined: %w", err)
+ }
+ _, inviteErr := bot.InviteUser(roomID, &mautrix.ReqInviteUser{
+ UserID: intent.UserID,
+ })
+ if inviteErr != nil {
+ return fmt.Errorf("failed to invite in ensure joined: %w", inviteErr)
+ }
+ resp, err = intent.JoinRoomByID(roomID)
+ if err != nil {
+ return fmt.Errorf("failed to ensure joined after invite: %w", err)
+ }
+ }
+ intent.as.StateStore.SetMembership(resp.RoomID, intent.UserID, event.MembershipJoin)
+ return nil
+}
+
+func (intent *IntentAPI) AddDoublePuppetValue(into interface{}) interface{} {
+ if !intent.IsCustomPuppet || intent.as.DoublePuppetValue == "" {
+ return into
+ }
+ switch val := into.(type) {
+ case *map[string]interface{}:
+ if *val == nil {
+ valNonPtr := make(map[string]interface{})
+ *val = valNonPtr
+ }
+ (*val)[DoublePuppetKey] = intent.as.DoublePuppetValue
+ return val
+ case map[string]interface{}:
+ val[DoublePuppetKey] = intent.as.DoublePuppetValue
+ return val
+ case *event.Content:
+ if val.Raw == nil {
+ val.Raw = make(map[string]interface{})
+ }
+ val.Raw[DoublePuppetKey] = intent.as.DoublePuppetValue
+ return val
+ case event.Content:
+ if val.Raw == nil {
+ val.Raw = make(map[string]interface{})
+ }
+ val.Raw[DoublePuppetKey] = intent.as.DoublePuppetValue
+ return val
+ default:
+ return &event.Content{
+ Raw: map[string]interface{}{
+ DoublePuppetKey: intent.as.DoublePuppetValue,
+ },
+ Parsed: val,
+ }
+ }
+}
+
+func (intent *IntentAPI) SendMessageEvent(roomID id.RoomID, eventType event.Type, contentJSON interface{}) (*mautrix.RespSendEvent, error) {
+ if err := intent.EnsureJoined(roomID); err != nil {
+ return nil, err
+ }
+ contentJSON = intent.AddDoublePuppetValue(contentJSON)
+ return intent.Client.SendMessageEvent(roomID, eventType, contentJSON)
+}
+
+func (intent *IntentAPI) SendMassagedMessageEvent(roomID id.RoomID, eventType event.Type, contentJSON interface{}, ts int64) (*mautrix.RespSendEvent, error) {
+ if err := intent.EnsureJoined(roomID); err != nil {
+ return nil, err
+ }
+ contentJSON = intent.AddDoublePuppetValue(contentJSON)
+ return intent.Client.SendMessageEvent(roomID, eventType, contentJSON, mautrix.ReqSendEvent{Timestamp: ts})
+}
+
+func (intent *IntentAPI) SendStateEvent(roomID id.RoomID, eventType event.Type, stateKey string, contentJSON interface{}) (*mautrix.RespSendEvent, error) {
+ if eventType != event.StateMember || stateKey != string(intent.UserID) {
+ if err := intent.EnsureJoined(roomID); err != nil {
+ return nil, err
+ }
+ }
+ contentJSON = intent.AddDoublePuppetValue(contentJSON)
+ return intent.Client.SendStateEvent(roomID, eventType, stateKey, contentJSON)
+}
+
+func (intent *IntentAPI) SendMassagedStateEvent(roomID id.RoomID, eventType event.Type, stateKey string, contentJSON interface{}, ts int64) (*mautrix.RespSendEvent, error) {
+ if err := intent.EnsureJoined(roomID); err != nil {
+ return nil, err
+ }
+ contentJSON = intent.AddDoublePuppetValue(contentJSON)
+ return intent.Client.SendMassagedStateEvent(roomID, eventType, stateKey, contentJSON, ts)
+}
+
+func (intent *IntentAPI) StateEvent(roomID id.RoomID, eventType event.Type, stateKey string, outContent interface{}) error {
+ if err := intent.EnsureJoined(roomID); err != nil {
+ return err
+ }
+ return intent.Client.StateEvent(roomID, eventType, stateKey, outContent)
+}
+
+func (intent *IntentAPI) State(roomID id.RoomID) (mautrix.RoomStateMap, error) {
+ if err := intent.EnsureJoined(roomID); err != nil {
+ return nil, err
+ }
+ return intent.Client.State(roomID)
+}
+
+func (intent *IntentAPI) SendCustomMembershipEvent(roomID id.RoomID, target id.UserID, membership event.Membership, reason string, extraContent ...map[string]interface{}) (*mautrix.RespSendEvent, error) {
+ content := &event.MemberEventContent{
+ Membership: membership,
+ Reason: reason,
+ }
+ memberContent, ok := intent.as.StateStore.TryGetMember(roomID, target)
+ if !ok {
+ if intent.as.GetProfile != nil {
+ memberContent = intent.as.GetProfile(target, roomID)
+ ok = memberContent != nil
+ }
+ if !ok {
+ profile, err := intent.GetProfile(target)
+ if err != nil {
+ intent.Log.Debug().Err(err).
+ Str("target_user_id", target.String()).
+ Str("membership", string(membership)).
+ Msg("Failed to get profile to fill new membership event")
+ } else {
+ content.Displayname = profile.DisplayName
+ content.AvatarURL = profile.AvatarURL.CUString()
+ }
+ }
+ }
+ if ok && memberContent != nil {
+ content.Displayname = memberContent.Displayname
+ content.AvatarURL = memberContent.AvatarURL
+ }
+ var extra map[string]interface{}
+ if len(extraContent) > 0 {
+ extra = extraContent[0]
+ }
+ return intent.SendStateEvent(roomID, event.StateMember, target.String(), &event.Content{
+ Parsed: content,
+ Raw: extra,
+ })
+}
+
+func (intent *IntentAPI) JoinRoomByID(roomID id.RoomID, extraContent ...map[string]interface{}) (resp *mautrix.RespJoinRoom, err error) {
+ if intent.IsCustomPuppet || len(extraContent) > 0 {
+ _, err = intent.SendCustomMembershipEvent(roomID, intent.UserID, event.MembershipJoin, "", extraContent...)
+ return &mautrix.RespJoinRoom{}, err
+ }
+ return intent.Client.JoinRoomByID(roomID)
+}
+
+func (intent *IntentAPI) LeaveRoom(roomID id.RoomID, extra ...interface{}) (resp *mautrix.RespLeaveRoom, err error) {
+ var extraContent map[string]interface{}
+ leaveReq := &mautrix.ReqLeave{}
+ for _, item := range extra {
+ switch val := item.(type) {
+ case map[string]interface{}:
+ extraContent = val
+ case *mautrix.ReqLeave:
+ leaveReq = val
+ }
+ }
+ if intent.IsCustomPuppet || extraContent != nil {
+ _, err = intent.SendCustomMembershipEvent(roomID, intent.UserID, event.MembershipLeave, leaveReq.Reason, extraContent)
+ return &mautrix.RespLeaveRoom{}, err
+ }
+ return intent.Client.LeaveRoom(roomID, leaveReq)
+}
+
+func (intent *IntentAPI) InviteUser(roomID id.RoomID, req *mautrix.ReqInviteUser, extraContent ...map[string]interface{}) (resp *mautrix.RespInviteUser, err error) {
+ if intent.IsCustomPuppet || len(extraContent) > 0 {
+ _, err = intent.SendCustomMembershipEvent(roomID, req.UserID, event.MembershipInvite, req.Reason, extraContent...)
+ return &mautrix.RespInviteUser{}, err
+ }
+ return intent.Client.InviteUser(roomID, req)
+}
+
+func (intent *IntentAPI) KickUser(roomID id.RoomID, req *mautrix.ReqKickUser, extraContent ...map[string]interface{}) (resp *mautrix.RespKickUser, err error) {
+ if intent.IsCustomPuppet || len(extraContent) > 0 {
+ _, err = intent.SendCustomMembershipEvent(roomID, req.UserID, event.MembershipLeave, req.Reason, extraContent...)
+ return &mautrix.RespKickUser{}, err
+ }
+ return intent.Client.KickUser(roomID, req)
+}
+
+func (intent *IntentAPI) BanUser(roomID id.RoomID, req *mautrix.ReqBanUser, extraContent ...map[string]interface{}) (resp *mautrix.RespBanUser, err error) {
+ if intent.IsCustomPuppet || len(extraContent) > 0 {
+ _, err = intent.SendCustomMembershipEvent(roomID, req.UserID, event.MembershipBan, req.Reason, extraContent...)
+ return &mautrix.RespBanUser{}, err
+ }
+ return intent.Client.BanUser(roomID, req)
+}
+
+func (intent *IntentAPI) UnbanUser(roomID id.RoomID, req *mautrix.ReqUnbanUser, extraContent ...map[string]interface{}) (resp *mautrix.RespUnbanUser, err error) {
+ if intent.IsCustomPuppet || len(extraContent) > 0 {
+ _, err = intent.SendCustomMembershipEvent(roomID, req.UserID, event.MembershipLeave, req.Reason, extraContent...)
+ return &mautrix.RespUnbanUser{}, err
+ }
+ return intent.Client.UnbanUser(roomID, req)
+}
+
+func (intent *IntentAPI) Member(roomID id.RoomID, userID id.UserID) *event.MemberEventContent {
+ member, ok := intent.as.StateStore.TryGetMember(roomID, userID)
+ if !ok {
+ _ = intent.StateEvent(roomID, event.StateMember, string(userID), &member)
+ }
+ return member
+}
+
+func (intent *IntentAPI) PowerLevels(roomID id.RoomID) (pl *event.PowerLevelsEventContent, err error) {
+ pl = intent.as.StateStore.GetPowerLevels(roomID)
+ if pl == nil {
+ pl = &event.PowerLevelsEventContent{}
+ err = intent.StateEvent(roomID, event.StatePowerLevels, "", pl)
+ }
+ return
+}
+
+func (intent *IntentAPI) SetPowerLevels(roomID id.RoomID, levels *event.PowerLevelsEventContent) (resp *mautrix.RespSendEvent, err error) {
+ return intent.SendStateEvent(roomID, event.StatePowerLevels, "", &levels)
+}
+
+func (intent *IntentAPI) SetPowerLevel(roomID id.RoomID, userID id.UserID, level int) (*mautrix.RespSendEvent, error) {
+ pl, err := intent.PowerLevels(roomID)
+ if err != nil {
+ return nil, err
+ }
+
+ if pl.GetUserLevel(userID) != level {
+ pl.SetUserLevel(userID, level)
+ return intent.SendStateEvent(roomID, event.StatePowerLevels, "", &pl)
+ }
+ return nil, nil
+}
+
+func (intent *IntentAPI) SendText(roomID id.RoomID, text string) (*mautrix.RespSendEvent, error) {
+ if err := intent.EnsureJoined(roomID); err != nil {
+ return nil, err
+ }
+ return intent.Client.SendText(roomID, text)
+}
+
+func (intent *IntentAPI) SendNotice(roomID id.RoomID, text string) (*mautrix.RespSendEvent, error) {
+ if err := intent.EnsureJoined(roomID); err != nil {
+ return nil, err
+ }
+ return intent.Client.SendNotice(roomID, text)
+}
+
+func (intent *IntentAPI) RedactEvent(roomID id.RoomID, eventID id.EventID, extra ...mautrix.ReqRedact) (*mautrix.RespSendEvent, error) {
+ if err := intent.EnsureJoined(roomID); err != nil {
+ return nil, err
+ }
+ var req mautrix.ReqRedact
+ if len(extra) > 0 {
+ req = extra[0]
+ }
+ intent.AddDoublePuppetValue(&req.Extra)
+ return intent.Client.RedactEvent(roomID, eventID, req)
+}
+
+func (intent *IntentAPI) SetRoomName(roomID id.RoomID, roomName string) (*mautrix.RespSendEvent, error) {
+ return intent.SendStateEvent(roomID, event.StateRoomName, "", map[string]interface{}{
+ "name": roomName,
+ })
+}
+
+func (intent *IntentAPI) SetRoomAvatar(roomID id.RoomID, avatarURL id.ContentURI) (*mautrix.RespSendEvent, error) {
+ return intent.SendStateEvent(roomID, event.StateRoomAvatar, "", map[string]interface{}{
+ "url": avatarURL.String(),
+ })
+}
+
+func (intent *IntentAPI) SetRoomTopic(roomID id.RoomID, topic string) (*mautrix.RespSendEvent, error) {
+ return intent.SendStateEvent(roomID, event.StateTopic, "", map[string]interface{}{
+ "topic": topic,
+ })
+}
+
+func (intent *IntentAPI) SetDisplayName(displayName string) error {
+ if err := intent.EnsureRegistered(); err != nil {
+ return err
+ }
+ resp, err := intent.Client.GetOwnDisplayName()
+ if err != nil {
+ return fmt.Errorf("failed to check current displayname: %w", err)
+ } else if resp.DisplayName == displayName {
+ // No need to update
+ return nil
+ }
+ return intent.Client.SetDisplayName(displayName)
+}
+
+func (intent *IntentAPI) SetAvatarURL(avatarURL id.ContentURI) error {
+ if err := intent.EnsureRegistered(); err != nil {
+ return err
+ }
+ resp, err := intent.Client.GetOwnAvatarURL()
+ if err != nil {
+ return fmt.Errorf("failed to check current avatar URL: %w", err)
+ } else if resp.FileID == avatarURL.FileID && resp.Homeserver == avatarURL.Homeserver {
+ // No need to update
+ return nil
+ }
+ return intent.Client.SetAvatarURL(avatarURL)
+}
+
+func (intent *IntentAPI) Whoami() (*mautrix.RespWhoami, error) {
+ if err := intent.EnsureRegistered(); err != nil {
+ return nil, err
+ }
+ return intent.Client.Whoami()
+}
+
+func (intent *IntentAPI) EnsureInvited(roomID id.RoomID, userID id.UserID) error {
+ if !intent.as.StateStore.IsInvited(roomID, userID) {
+ _, err := intent.InviteUser(roomID, &mautrix.ReqInviteUser{
+ UserID: userID,
+ })
+ if httpErr, ok := err.(mautrix.HTTPError); ok &&
+ httpErr.RespError != nil &&
+ (strings.Contains(httpErr.RespError.Err, "is already in the room") || strings.Contains(httpErr.RespError.Err, "is already joined to room")) {
+ return nil
+ }
+ return err
+ }
+ return nil
+}
diff --git a/vendor/maunium.net/go/mautrix/appservice/protocol.go b/vendor/maunium.net/go/mautrix/appservice/protocol.go
new file mode 100644
index 00000000..7a9891ef
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/appservice/protocol.go
@@ -0,0 +1,152 @@
+// Copyright (c) 2023 Tulir Asokan
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package appservice
+
+import (
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/rs/zerolog"
+
+ "maunium.net/go/mautrix"
+ "maunium.net/go/mautrix/event"
+ "maunium.net/go/mautrix/id"
+)
+
+type OTKCountMap = map[id.UserID]map[id.DeviceID]mautrix.OTKCount
+type FallbackKeyMap = map[id.UserID]map[id.DeviceID][]id.KeyAlgorithm
+
+// Transaction contains a list of events.
+type Transaction struct {
+ Events []*event.Event `json:"events"`
+ EphemeralEvents []*event.Event `json:"ephemeral,omitempty"`
+ ToDeviceEvents []*event.Event `json:"to_device,omitempty"`
+
+ DeviceLists *mautrix.DeviceLists `json:"device_lists,omitempty"`
+ DeviceOTKCount OTKCountMap `json:"device_one_time_keys_count,omitempty"`
+ FallbackKeys FallbackKeyMap `json:"device_unused_fallback_key_types,omitempty"`
+
+ MSC2409EphemeralEvents []*event.Event `json:"de.sorunome.msc2409.ephemeral,omitempty"`
+ MSC2409ToDeviceEvents []*event.Event `json:"de.sorunome.msc2409.to_device,omitempty"`
+ MSC3202DeviceLists *mautrix.DeviceLists `json:"org.matrix.msc3202.device_lists,omitempty"`
+ MSC3202DeviceOTKCount OTKCountMap `json:"org.matrix.msc3202.device_one_time_keys_count,omitempty"`
+ MSC3202FallbackKeys FallbackKeyMap `json:"org.matrix.msc3202.device_unused_fallback_key_types,omitempty"`
+}
+
+func (txn *Transaction) MarshalZerologObject(ctx *zerolog.Event) {
+ ctx.Int("pdu", len(txn.Events))
+ if txn.EphemeralEvents != nil {
+ ctx.Int("edu", len(txn.EphemeralEvents))
+ } else if txn.MSC2409EphemeralEvents != nil {
+ ctx.Int("unstable_edu", len(txn.MSC2409EphemeralEvents))
+ }
+ if txn.ToDeviceEvents != nil {
+ ctx.Int("to_device", len(txn.ToDeviceEvents))
+ } else if txn.MSC2409ToDeviceEvents != nil {
+ ctx.Int("unstable_to_device", len(txn.MSC2409ToDeviceEvents))
+ }
+ if len(txn.DeviceOTKCount) > 0 {
+ ctx.Int("otk_count_users", len(txn.DeviceOTKCount))
+ } else if len(txn.MSC3202DeviceOTKCount) > 0 {
+ ctx.Int("unstable_otk_count_users", len(txn.MSC3202DeviceOTKCount))
+ }
+ if txn.DeviceLists != nil {
+ ctx.Int("device_changes", len(txn.DeviceLists.Changed))
+ } else if txn.MSC3202DeviceLists != nil {
+ ctx.Int("unstable_device_changes", len(txn.MSC3202DeviceLists.Changed))
+ }
+ if txn.FallbackKeys != nil {
+ ctx.Int("fallback_key_users", len(txn.FallbackKeys))
+ } else if txn.MSC3202FallbackKeys != nil {
+ ctx.Int("unstable_fallback_key_users", len(txn.MSC3202FallbackKeys))
+ }
+}
+
+func (txn *Transaction) ContentString() string {
+ var parts []string
+ if len(txn.Events) > 0 {
+ parts = append(parts, fmt.Sprintf("%d PDUs", len(txn.Events)))
+ }
+ if len(txn.EphemeralEvents) > 0 {
+ parts = append(parts, fmt.Sprintf("%d EDUs", len(txn.EphemeralEvents)))
+ } else if len(txn.MSC2409EphemeralEvents) > 0 {
+ parts = append(parts, fmt.Sprintf("%d EDUs (unstable)", len(txn.MSC2409EphemeralEvents)))
+ }
+ if len(txn.ToDeviceEvents) > 0 {
+ parts = append(parts, fmt.Sprintf("%d to-device events", len(txn.ToDeviceEvents)))
+ } else if len(txn.MSC2409ToDeviceEvents) > 0 {
+ parts = append(parts, fmt.Sprintf("%d to-device events (unstable)", len(txn.MSC2409ToDeviceEvents)))
+ }
+ if len(txn.DeviceOTKCount) > 0 {
+ parts = append(parts, fmt.Sprintf("OTK counts for %d users", len(txn.DeviceOTKCount)))
+ } else if len(txn.MSC3202DeviceOTKCount) > 0 {
+ parts = append(parts, fmt.Sprintf("OTK counts for %d users (unstable)", len(txn.MSC3202DeviceOTKCount)))
+ }
+ if txn.DeviceLists != nil {
+ parts = append(parts, fmt.Sprintf("%d device list changes", len(txn.DeviceLists.Changed)))
+ } else if txn.MSC3202DeviceLists != nil {
+ parts = append(parts, fmt.Sprintf("%d device list changes (unstable)", len(txn.MSC3202DeviceLists.Changed)))
+ }
+ if txn.FallbackKeys != nil {
+ parts = append(parts, fmt.Sprintf("unused fallback key counts for %d users", len(txn.FallbackKeys)))
+ } else if txn.MSC3202FallbackKeys != nil {
+ parts = append(parts, fmt.Sprintf("unused fallback key counts for %d users (unstable)", len(txn.MSC3202FallbackKeys)))
+ }
+ return strings.Join(parts, ", ")
+}
+
+// EventListener is a function that receives events.
+type EventListener func(evt *event.Event)
+
+// WriteBlankOK writes a blank OK message as a reply to a HTTP request.
+func WriteBlankOK(w http.ResponseWriter) {
+ w.Header().Add("Content-Type", "application/json")
+ w.WriteHeader(http.StatusOK)
+ _, _ = w.Write([]byte("{}"))
+}
+
+// Respond responds to a HTTP request with a JSON object.
+func Respond(w http.ResponseWriter, data interface{}) error {
+ w.Header().Add("Content-Type", "application/json")
+ dataStr, err := json.Marshal(data)
+ if err != nil {
+ return err
+ }
+ _, err = w.Write(dataStr)
+ return err
+}
+
+// Error represents a Matrix protocol error.
+type Error struct {
+ HTTPStatus int `json:"-"`
+ ErrorCode ErrorCode `json:"errcode"`
+ Message string `json:"error"`
+}
+
+func (err Error) Write(w http.ResponseWriter) {
+ w.Header().Add("Content-Type", "application/json")
+ w.WriteHeader(err.HTTPStatus)
+ _ = Respond(w, &err)
+}
+
+// ErrorCode is the machine-readable code in an Error.
+type ErrorCode string
+
+// Native ErrorCodes
+const (
+ ErrUnknownToken ErrorCode = "M_UNKNOWN_TOKEN"
+ ErrBadJSON ErrorCode = "M_BAD_JSON"
+ ErrNotJSON ErrorCode = "M_NOT_JSON"
+ ErrUnknown ErrorCode = "M_UNKNOWN"
+)
+
+// Custom ErrorCodes
+const (
+ ErrNoTransactionID ErrorCode = "NET.MAUNIUM.NO_TRANSACTION_ID"
+)
diff --git a/vendor/maunium.net/go/mautrix/appservice/registration.go b/vendor/maunium.net/go/mautrix/appservice/registration.go
new file mode 100644
index 00000000..f9c93fe4
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/appservice/registration.go
@@ -0,0 +1,100 @@
+// Copyright (c) 2022 Tulir Asokan
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package appservice
+
+import (
+ "os"
+ "regexp"
+
+ "gopkg.in/yaml.v3"
+
+ "maunium.net/go/mautrix/util"
+)
+
+// Registration contains the data in a Matrix appservice registration.
+// See https://spec.matrix.org/v1.2/application-service-api/#registration
+type Registration struct {
+ ID string `yaml:"id" json:"id"`
+ URL string `yaml:"url" json:"url"`
+ AppToken string `yaml:"as_token" json:"as_token"`
+ ServerToken string `yaml:"hs_token" json:"hs_token"`
+ SenderLocalpart string `yaml:"sender_localpart" json:"sender_localpart"`
+ RateLimited *bool `yaml:"rate_limited,omitempty" json:"rate_limited,omitempty"`
+ Namespaces Namespaces `yaml:"namespaces" json:"namespaces"`
+ Protocols []string `yaml:"protocols,omitempty" json:"protocols,omitempty"`
+
+ SoruEphemeralEvents bool `yaml:"de.sorunome.msc2409.push_ephemeral,omitempty" json:"de.sorunome.msc2409.push_ephemeral,omitempty"`
+ EphemeralEvents bool `yaml:"push_ephemeral,omitempty" json:"push_ephemeral,omitempty"`
+}
+
+// CreateRegistration creates a Registration with random appservice and homeserver tokens.
+func CreateRegistration() *Registration {
+ return &Registration{
+ AppToken: util.RandomString(64),
+ ServerToken: util.RandomString(64),
+ }
+}
+
+// LoadRegistration loads a YAML file and turns it into a Registration.
+func LoadRegistration(path string) (*Registration, error) {
+ data, err := os.ReadFile(path)
+ if err != nil {
+ return nil, err
+ }
+
+ reg := &Registration{}
+ err = yaml.Unmarshal(data, reg)
+ if err != nil {
+ return nil, err
+ }
+ return reg, nil
+}
+
+// Save saves this Registration into a file at the given path.
+func (reg *Registration) Save(path string) error {
+ data, err := yaml.Marshal(reg)
+ if err != nil {
+ return err
+ }
+ return os.WriteFile(path, data, 0600)
+}
+
+// YAML returns the registration in YAML format.
+func (reg *Registration) YAML() (string, error) {
+ data, err := yaml.Marshal(reg)
+ if err != nil {
+ return "", err
+ }
+ return string(data), nil
+}
+
+// Namespaces contains the three areas that appservices can reserve parts of.
+type Namespaces struct {
+ UserIDs NamespaceList `yaml:"users,omitempty" json:"users,omitempty"`
+ RoomAliases NamespaceList `yaml:"aliases,omitempty" json:"aliases,omitempty"`
+ RoomIDs NamespaceList `yaml:"rooms,omitempty" json:"rooms,omitempty"`
+}
+
+// Namespace is a reserved namespace in any area.
+type Namespace struct {
+ Regex string `yaml:"regex" json:"regex"`
+ Exclusive bool `yaml:"exclusive" json:"exclusive"`
+}
+
+type NamespaceList []Namespace
+
+func (nsl *NamespaceList) Register(regex *regexp.Regexp, exclusive bool) {
+ ns := Namespace{
+ Regex: regex.String(),
+ Exclusive: exclusive,
+ }
+ if nsl == nil {
+ *nsl = []Namespace{ns}
+ } else {
+ *nsl = append(*nsl, ns)
+ }
+}
diff --git a/vendor/maunium.net/go/mautrix/appservice/txnid.go b/vendor/maunium.net/go/mautrix/appservice/txnid.go
new file mode 100644
index 00000000..213703c5
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/appservice/txnid.go
@@ -0,0 +1,43 @@
+// Copyright (c) 2021 Tulir Asokan
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package appservice
+
+import "sync"
+
+type TransactionIDCache struct {
+ array []string
+ arrayPtr int
+ hash map[string]struct{}
+ lock sync.RWMutex
+}
+
+func NewTransactionIDCache(size int) *TransactionIDCache {
+ return &TransactionIDCache{
+ array: make([]string, size),
+ hash: make(map[string]struct{}),
+ }
+}
+
+func (txnIDC *TransactionIDCache) IsProcessed(txnID string) bool {
+ txnIDC.lock.RLock()
+ _, exists := txnIDC.hash[txnID]
+ txnIDC.lock.RUnlock()
+ return exists
+}
+
+func (txnIDC *TransactionIDCache) MarkProcessed(txnID string) {
+ txnIDC.lock.Lock()
+ txnIDC.hash[txnID] = struct{}{}
+ if txnIDC.array[txnIDC.arrayPtr] != "" {
+ for i := 0; i < len(txnIDC.array)/8; i++ {
+ delete(txnIDC.hash, txnIDC.array[txnIDC.arrayPtr+i])
+ txnIDC.array[txnIDC.arrayPtr+i] = ""
+ }
+ }
+ txnIDC.array[txnIDC.arrayPtr] = txnID
+ txnIDC.lock.Unlock()
+}
diff --git a/vendor/maunium.net/go/mautrix/appservice/websocket.go b/vendor/maunium.net/go/mautrix/appservice/websocket.go
new file mode 100644
index 00000000..671222b8
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/appservice/websocket.go
@@ -0,0 +1,408 @@
+// Copyright (c) 2023 Tulir Asokan
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package appservice
+
+import (
+ "context"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "net/http"
+ "net/url"
+ "path/filepath"
+ "strings"
+ "sync"
+ "sync/atomic"
+ "time"
+
+ "github.com/gorilla/websocket"
+ "github.com/rs/zerolog"
+ "github.com/tidwall/gjson"
+ "github.com/tidwall/sjson"
+)
+
+type WebsocketRequest struct {
+ ReqID int `json:"id,omitempty"`
+ Command string `json:"command"`
+ Data interface{} `json:"data"`
+
+ Deadline time.Duration `json:"-"`
+}
+
+type WebsocketCommand struct {
+ ReqID int `json:"id,omitempty"`
+ Command string `json:"command"`
+ Data json.RawMessage `json:"data"`
+
+ Ctx context.Context `json:"-"`
+}
+
+func (wsc *WebsocketCommand) MakeResponse(ok bool, data interface{}) *WebsocketRequest {
+ if wsc.ReqID == 0 || wsc.Command == "response" || wsc.Command == "error" {
+ return nil
+ }
+ cmd := "response"
+ if !ok {
+ cmd = "error"
+ }
+ if err, isError := data.(error); isError {
+ var errorData json.RawMessage
+ var jsonErr error
+ unwrappedErr := err
+ var prefixMessage string
+ for unwrappedErr != nil {
+ errorData, jsonErr = json.Marshal(unwrappedErr)
+ if errorData != nil && len(errorData) > 2 && jsonErr == nil {
+ prefixMessage = strings.Replace(err.Error(), unwrappedErr.Error(), "", 1)
+ prefixMessage = strings.TrimRight(prefixMessage, ": ")
+ break
+ }
+ unwrappedErr = errors.Unwrap(unwrappedErr)
+ }
+ if errorData != nil {
+ if !gjson.GetBytes(errorData, "message").Exists() {
+ errorData, _ = sjson.SetBytes(errorData, "message", err.Error())
+ } // else: marshaled error contains a message already
+ } else {
+ errorData, _ = sjson.SetBytes(nil, "message", err.Error())
+ }
+ if len(prefixMessage) > 0 {
+ errorData, _ = sjson.SetBytes(errorData, "prefix_message", prefixMessage)
+ }
+ data = errorData
+ }
+ return &WebsocketRequest{
+ ReqID: wsc.ReqID,
+ Command: cmd,
+ Data: data,
+ }
+}
+
+type WebsocketTransaction struct {
+ Status string `json:"status"`
+ TxnID string `json:"txn_id"`
+ Transaction
+}
+
+type WebsocketTransactionResponse struct {
+ TxnID string `json:"txn_id"`
+}
+
+type WebsocketMessage struct {
+ WebsocketTransaction
+ WebsocketCommand
+}
+
+const (
+ WebsocketCloseConnReplaced = 4001
+ WebsocketCloseTxnNotAcknowledged = 4002
+)
+
+type MeowWebsocketCloseCode string
+
+const (
+ MeowServerShuttingDown MeowWebsocketCloseCode = "server_shutting_down"
+ MeowConnectionReplaced MeowWebsocketCloseCode = "conn_replaced"
+ MeowTxnNotAcknowledged MeowWebsocketCloseCode = "transactions_not_acknowledged"
+)
+
+var (
+ ErrWebsocketManualStop = errors.New("the websocket was disconnected manually")
+ ErrWebsocketOverridden = errors.New("a new call to StartWebsocket overrode the previous connection")
+ ErrWebsocketUnknownError = errors.New("an unknown error occurred")
+
+ ErrWebsocketNotConnected = errors.New("websocket not connected")
+ ErrWebsocketClosed = errors.New("websocket closed before response received")
+)
+
+func (mwcc MeowWebsocketCloseCode) String() string {
+ switch mwcc {
+ case MeowServerShuttingDown:
+ return "the server is shutting down"
+ case MeowConnectionReplaced:
+ return "the connection was replaced by another client"
+ case MeowTxnNotAcknowledged:
+ return "transactions were not acknowledged"
+ default:
+ return string(mwcc)
+ }
+}
+
+type CloseCommand struct {
+ Code int `json:"-"`
+ Command string `json:"command"`
+ Status MeowWebsocketCloseCode `json:"status"`
+}
+
+func (cc CloseCommand) Error() string {
+ return fmt.Sprintf("websocket: close %d: %s", cc.Code, cc.Status.String())
+}
+
+func parseCloseError(err error) error {
+ closeError := &websocket.CloseError{}
+ if !errors.As(err, &closeError) {
+ return err
+ }
+ var closeCommand CloseCommand
+ closeCommand.Code = closeError.Code
+ closeCommand.Command = "disconnect"
+ if len(closeError.Text) > 0 {
+ jsonErr := json.Unmarshal([]byte(closeError.Text), &closeCommand)
+ if jsonErr != nil {
+ return err
+ }
+ }
+ if len(closeCommand.Status) == 0 {
+ if closeCommand.Code == WebsocketCloseConnReplaced {
+ closeCommand.Status = MeowConnectionReplaced
+ } else if closeCommand.Code == websocket.CloseServiceRestart {
+ closeCommand.Status = MeowServerShuttingDown
+ }
+ }
+ return &closeCommand
+}
+
+func (as *AppService) HasWebsocket() bool {
+ return as.ws != nil
+}
+
+func (as *AppService) SendWebsocket(cmd *WebsocketRequest) error {
+ ws := as.ws
+ if cmd == nil {
+ return nil
+ } else if ws == nil {
+ return ErrWebsocketNotConnected
+ }
+ as.wsWriteLock.Lock()
+ defer as.wsWriteLock.Unlock()
+ if cmd.Deadline == 0 {
+ cmd.Deadline = 3 * time.Minute
+ }
+ _ = ws.SetWriteDeadline(time.Now().Add(cmd.Deadline))
+ return ws.WriteJSON(cmd)
+}
+
+func (as *AppService) clearWebsocketResponseWaiters() {
+ as.websocketRequestsLock.Lock()
+ for _, waiter := range as.websocketRequests {
+ waiter <- &WebsocketCommand{Command: "__websocket_closed"}
+ }
+ as.websocketRequests = make(map[int]chan<- *WebsocketCommand)
+ as.websocketRequestsLock.Unlock()
+}
+
+func (as *AppService) addWebsocketResponseWaiter(reqID int, waiter chan<- *WebsocketCommand) {
+ as.websocketRequestsLock.Lock()
+ as.websocketRequests[reqID] = waiter
+ as.websocketRequestsLock.Unlock()
+}
+
+func (as *AppService) removeWebsocketResponseWaiter(reqID int, waiter chan<- *WebsocketCommand) {
+ as.websocketRequestsLock.Lock()
+ existingWaiter, ok := as.websocketRequests[reqID]
+ if ok && existingWaiter == waiter {
+ delete(as.websocketRequests, reqID)
+ }
+ close(waiter)
+ as.websocketRequestsLock.Unlock()
+}
+
+type ErrorResponse struct {
+ Code string `json:"code"`
+ Message string `json:"message"`
+}
+
+func (er *ErrorResponse) Error() string {
+ return fmt.Sprintf("%s: %s", er.Code, er.Message)
+}
+
+func (as *AppService) RequestWebsocket(ctx context.Context, cmd *WebsocketRequest, response interface{}) error {
+ cmd.ReqID = int(atomic.AddInt32(&as.websocketRequestID, 1))
+ respChan := make(chan *WebsocketCommand, 1)
+ as.addWebsocketResponseWaiter(cmd.ReqID, respChan)
+ defer as.removeWebsocketResponseWaiter(cmd.ReqID, respChan)
+ err := as.SendWebsocket(cmd)
+ if err != nil {
+ return err
+ }
+ select {
+ case resp := <-respChan:
+ if resp.Command == "__websocket_closed" {
+ return ErrWebsocketClosed
+ } else if resp.Command == "error" {
+ var respErr ErrorResponse
+ err = json.Unmarshal(resp.Data, &respErr)
+ if err != nil {
+ return fmt.Errorf("failed to parse error JSON: %w", err)
+ }
+ return &respErr
+ } else if response != nil {
+ err = json.Unmarshal(resp.Data, &response)
+ if err != nil {
+ return fmt.Errorf("failed to parse response JSON: %w", err)
+ }
+ return nil
+ } else {
+ return nil
+ }
+ case <-ctx.Done():
+ return ctx.Err()
+ }
+}
+
+func (as *AppService) unknownCommandHandler(cmd WebsocketCommand) (bool, interface{}) {
+ zerolog.Ctx(cmd.Ctx).Warn().Msg("No handler for websocket command")
+ return false, fmt.Errorf("unknown request type")
+}
+
+func (as *AppService) SetWebsocketCommandHandler(cmd string, handler WebsocketHandler) {
+ as.websocketHandlersLock.Lock()
+ as.websocketHandlers[cmd] = handler
+ as.websocketHandlersLock.Unlock()
+}
+
+func (as *AppService) consumeWebsocket(stopFunc func(error), ws *websocket.Conn) {
+ defer stopFunc(ErrWebsocketUnknownError)
+ ctx := context.Background()
+ for {
+ var msg WebsocketMessage
+ err := ws.ReadJSON(&msg)
+ if err != nil {
+ as.Log.Debug().Err(err).Msg("Error reading from websocket")
+ stopFunc(parseCloseError(err))
+ return
+ }
+ with := as.Log.With().
+ Int("req_id", msg.ReqID).
+ Str("ws_command", msg.Command)
+ if msg.TxnID != "" {
+ with = with.Str("transaction_id", msg.TxnID)
+ }
+ log := with.Logger()
+ ctx = log.WithContext(ctx)
+ if msg.Command == "" || msg.Command == "transaction" {
+ if msg.TxnID == "" || !as.txnIDC.IsProcessed(msg.TxnID) {
+ as.handleTransaction(ctx, msg.TxnID, &msg.Transaction)
+ } else {
+ log.Debug().
+ Object("content", &msg.Transaction).
+ Msg("Ignoring duplicate transaction")
+ }
+ go func() {
+ err = as.SendWebsocket(msg.MakeResponse(true, &WebsocketTransactionResponse{TxnID: msg.TxnID}))
+ if err != nil {
+ log.Warn().Err(err).Msg("Failed to send response to websocket transaction")
+ } else {
+ log.Debug().Msg("Sent response to transaction")
+ }
+ }()
+ } else if msg.Command == "connect" {
+ log.Debug().Msg("Websocket connect confirmation received")
+ } else if msg.Command == "response" || msg.Command == "error" {
+ as.websocketRequestsLock.RLock()
+ respChan, ok := as.websocketRequests[msg.ReqID]
+ if ok {
+ select {
+ case respChan <- &msg.WebsocketCommand:
+ default:
+ log.Warn().Msg("Failed to handle response: channel didn't accept response")
+ }
+ } else {
+ log.Warn().Msg("Dropping response to unknown request ID")
+ }
+ as.websocketRequestsLock.RUnlock()
+ } else {
+ log.Debug().Msg("Received websocket command")
+ as.websocketHandlersLock.RLock()
+ handler, ok := as.websocketHandlers[msg.Command]
+ as.websocketHandlersLock.RUnlock()
+ if !ok {
+ handler = as.unknownCommandHandler
+ }
+ go func() {
+ okResp, data := handler(msg.WebsocketCommand)
+ err = as.SendWebsocket(msg.MakeResponse(okResp, data))
+ if err != nil {
+ log.Error().Err(err).Msg("Failed to send response to websocket command")
+ } else if okResp {
+ log.Debug().Msg("Sent success response to websocket command")
+ } else {
+ log.Debug().Msg("Sent error response to websocket command")
+ }
+ }()
+ }
+ }
+}
+
+func (as *AppService) StartWebsocket(baseURL string, onConnect func()) error {
+ parsed, err := url.Parse(baseURL)
+ if err != nil {
+ return fmt.Errorf("failed to parse URL: %w", err)
+ }
+ parsed.Path = filepath.Join(parsed.Path, "_matrix/client/unstable/fi.mau.as_sync")
+ if parsed.Scheme == "http" {
+ parsed.Scheme = "ws"
+ } else if parsed.Scheme == "https" {
+ parsed.Scheme = "wss"
+ }
+ ws, resp, err := websocket.DefaultDialer.Dial(parsed.String(), http.Header{
+ "Authorization": []string{fmt.Sprintf("Bearer %s", as.Registration.AppToken)},
+ "User-Agent": []string{as.BotClient().UserAgent},
+
+ "X-Mautrix-Process-ID": []string{as.ProcessID},
+ "X-Mautrix-Websocket-Version": []string{"3"},
+ })
+ if resp != nil && resp.StatusCode >= 400 {
+ var errResp Error
+ err = json.NewDecoder(resp.Body).Decode(&errResp)
+ if err != nil {
+ return fmt.Errorf("websocket request returned HTTP %d with non-JSON body", resp.StatusCode)
+ } else {
+ return fmt.Errorf("websocket request returned %s (HTTP %d): %s", errResp.ErrorCode, resp.StatusCode, errResp.Message)
+ }
+ } else if err != nil {
+ return fmt.Errorf("failed to open websocket: %w", err)
+ }
+ if as.StopWebsocket != nil {
+ as.StopWebsocket(ErrWebsocketOverridden)
+ }
+ closeChan := make(chan error)
+ closeChanOnce := sync.Once{}
+ stopFunc := func(err error) {
+ closeChanOnce.Do(func() {
+ closeChan <- err
+ })
+ }
+ as.ws = ws
+ as.StopWebsocket = stopFunc
+ as.PrepareWebsocket()
+ as.Log.Debug().Msg("Appservice transaction websocket opened")
+
+ go as.consumeWebsocket(stopFunc, ws)
+
+ if onConnect != nil {
+ onConnect()
+ }
+
+ closeErr := <-closeChan
+
+ if as.ws == ws {
+ as.clearWebsocketResponseWaiters()
+ as.ws = nil
+ }
+
+ _ = ws.SetWriteDeadline(time.Now().Add(3 * time.Second))
+ err = ws.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseGoingAway, ""))
+ if err != nil && !errors.Is(err, websocket.ErrCloseSent) {
+ as.Log.Warn().Err(err).Msg("Error writing close message to websocket")
+ }
+ err = ws.Close()
+ if err != nil {
+ as.Log.Warn().Err(err).Msg("Error closing websocket")
+ }
+ return closeErr
+}
diff --git a/vendor/maunium.net/go/mautrix/client.go b/vendor/maunium.net/go/mautrix/client.go
new file mode 100644
index 00000000..2923eaea
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/client.go
@@ -0,0 +1,2023 @@
+// Package mautrix implements the Matrix Client-Server API.
+//
+// Specification can be found at https://spec.matrix.org/v1.2/client-server-api/
+package mautrix
+
+import (
+ "bytes"
+ "context"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "io"
+ "net/http"
+ "net/url"
+ "os"
+ "strconv"
+ "strings"
+ "sync/atomic"
+ "time"
+
+ "github.com/rs/zerolog"
+ "maunium.net/go/maulogger/v2/maulogadapt"
+
+ "maunium.net/go/mautrix/event"
+ "maunium.net/go/mautrix/id"
+ "maunium.net/go/mautrix/pushrules"
+)
+
+type CryptoHelper interface {
+ Encrypt(id.RoomID, event.Type, any) (*event.EncryptedEventContent, error)
+ Decrypt(*event.Event) (*event.Event, error)
+ WaitForSession(id.RoomID, id.SenderKey, id.SessionID, time.Duration) bool
+ RequestSession(id.RoomID, id.SenderKey, id.SessionID, id.UserID, id.DeviceID)
+ Init() error
+}
+
+// Deprecated: switch to zerolog
+type Logger interface {
+ Debugfln(message string, args ...interface{})
+}
+
+// Deprecated: switch to zerolog
+type WarnLogger interface {
+ Logger
+ Warnfln(message string, args ...interface{})
+}
+
+// Client represents a Matrix client.
+type Client struct {
+ HomeserverURL *url.URL // The base homeserver URL
+ UserID id.UserID // The user ID of the client. Used for forming HTTP paths which use the client's user ID.
+ DeviceID id.DeviceID // The device ID of the client.
+ AccessToken string // The access_token for the client.
+ UserAgent string // The value for the User-Agent header
+ 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 SyncStore // The thing which can store tokens/ids
+ StateStore StateStore
+ Crypto CryptoHelper
+
+ Log zerolog.Logger
+ // Deprecated: switch to the zerolog instance in Log
+ Logger Logger
+
+ RequestHook func(req *http.Request)
+ ResponseHook func(req *http.Request, resp *http.Response, duration time.Duration)
+
+ SyncPresence event.Presence
+
+ StreamSyncMinAge time.Duration
+
+ // Number of times that mautrix will retry any HTTP request
+ // if the request fails entirely or returns a HTTP gateway error (502-504)
+ DefaultHTTPRetries int
+ // Set to true to disable automatically sleeping on 429 errors.
+ IgnoreRateLimit bool
+
+ txnID int32
+
+ // Should the ?user_id= query parameter be set in requests?
+ // See https://spec.matrix.org/v1.6/application-service-api/#identity-assertion
+ SetAppServiceUserID bool
+
+ syncingID uint32 // Identifies the current Sync. Only one Sync can be active at any given time.
+}
+
+type ClientWellKnown struct {
+ Homeserver HomeserverInfo `json:"m.homeserver"`
+ IdentityServer IdentityServerInfo `json:"m.identity_server"`
+}
+
+type HomeserverInfo struct {
+ BaseURL string `json:"base_url"`
+}
+
+type IdentityServerInfo struct {
+ BaseURL string `json:"base_url"`
+}
+
+// DiscoverClientAPI resolves the client API URL from a Matrix server name.
+// Use ParseUserID to extract the server name from a user ID.
+// https://spec.matrix.org/v1.2/client-server-api/#server-discovery
+func DiscoverClientAPI(serverName string) (*ClientWellKnown, error) {
+ wellKnownURL := url.URL{
+ Scheme: "https",
+ Host: serverName,
+ Path: "/.well-known/matrix/client",
+ }
+
+ req, err := http.NewRequest("GET", wellKnownURL.String(), nil)
+ if err != nil {
+ return nil, err
+ }
+
+ req.Header.Set("Accept", "application/json")
+ req.Header.Set("User-Agent", DefaultUserAgent+" (.well-known fetcher)")
+
+ client := &http.Client{Timeout: 30 * time.Second}
+ resp, err := client.Do(req)
+ if err != nil {
+ return nil, err
+ }
+ defer resp.Body.Close()
+
+ if resp.StatusCode == http.StatusNotFound {
+ return nil, nil
+ }
+
+ data, err := io.ReadAll(resp.Body)
+ if err != nil {
+ return nil, err
+ }
+
+ var wellKnown ClientWellKnown
+ err = json.Unmarshal(data, &wellKnown)
+ if err != nil {
+ return nil, errors.New(".well-known response not JSON")
+ }
+
+ return &wellKnown, nil
+}
+
+// SetCredentials sets the user ID and access token on this client instance.
+//
+// Deprecated: use the StoreCredentials field in ReqLogin instead.
+func (cli *Client) SetCredentials(userID id.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 = ""
+ cli.DeviceID = ""
+}
+
+// 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 {
+ return cli.SyncWithContext(context.Background())
+}
+
+func (cli *Client) SyncWithContext(ctx context.Context) 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)
+ }
+ lastSuccessfulSync := time.Now().Add(-cli.StreamSyncMinAge - 1*time.Hour)
+ for {
+ streamResp := false
+ if cli.StreamSyncMinAge > 0 && time.Since(lastSuccessfulSync) > cli.StreamSyncMinAge {
+ cli.Log.Debug().Msg("Last sync is old, will stream next response")
+ streamResp = true
+ }
+ resSync, err := cli.FullSyncRequest(ReqSync{
+ Timeout: 30000,
+ Since: nextBatch,
+ FilterID: filterID,
+ FullState: false,
+ SetPresence: cli.SyncPresence,
+ Context: ctx,
+ StreamResponse: streamResp,
+ })
+ if err != nil {
+ if ctx.Err() != nil {
+ return ctx.Err()
+ }
+ duration, err2 := cli.Syncer.OnFailedSync(resSync, err)
+ if err2 != nil {
+ return err2
+ }
+ select {
+ case <-ctx.Done():
+ return ctx.Err()
+ case <-time.After(duration):
+ continue
+ }
+ }
+ lastSuccessfulSync = time.Now()
+
+ // 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 {
+ return atomic.AddUint32(&cli.syncingID, 1)
+}
+
+func (cli *Client) getSyncingID() uint32 {
+ return atomic.LoadUint32(&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()
+}
+
+type contextKey int
+
+const (
+ LogBodyContextKey contextKey = iota
+ LogRequestIDContextKey
+)
+
+func (cli *Client) LogRequest(req *http.Request) {
+ if cli.RequestHook != nil {
+ cli.RequestHook(req)
+ }
+ evt := zerolog.Ctx(req.Context()).Debug().
+ Str("method", req.Method).
+ Str("url", req.URL.String())
+ body := req.Context().Value(LogBodyContextKey)
+ if body != nil {
+ evt.Interface("body", body)
+ }
+ evt.Msg("Sending request")
+}
+
+func (cli *Client) LogRequestDone(req *http.Request, resp *http.Response, handlerErr error, contentLength int, duration time.Duration) {
+ if cli.ResponseHook != nil {
+ cli.ResponseHook(req, resp, duration)
+ }
+ mime := resp.Header.Get("Content-Type")
+ length := resp.ContentLength
+ if length == -1 && contentLength > 0 {
+ length = int64(contentLength)
+ }
+ path := strings.TrimPrefix(req.URL.Path, cli.HomeserverURL.Path)
+ path = strings.TrimPrefix(path, "/_matrix/client")
+ evt := zerolog.Ctx(req.Context()).Debug().
+ Str("method", req.Method).
+ Str("path", path).
+ Int("status_code", resp.StatusCode).
+ Int64("response_length", length).
+ Str("response_mime", mime).
+ Dur("duration", duration)
+ if handlerErr != nil {
+ evt.AnErr("body_parse_err", handlerErr)
+ }
+ evt.Msg("Request completed")
+}
+
+func (cli *Client) MakeRequest(method string, httpURL string, reqBody interface{}, resBody interface{}) ([]byte, error) {
+ return cli.MakeFullRequest(FullRequest{Method: method, URL: httpURL, RequestJSON: reqBody, ResponseJSON: resBody})
+}
+
+type ClientResponseHandler = func(req *http.Request, res *http.Response, responseJSON interface{}) ([]byte, error)
+
+type FullRequest struct {
+ Method string
+ URL string
+ Headers http.Header
+ RequestJSON interface{}
+ RequestBytes []byte
+ RequestBody io.Reader
+ RequestLength int64
+ ResponseJSON interface{}
+ Context context.Context
+ MaxAttempts int
+ SensitiveContent bool
+ Handler ClientResponseHandler
+ Logger *zerolog.Logger
+}
+
+var requestID int32
+var logSensitiveContent = os.Getenv("MAUTRIX_LOG_SENSITIVE_CONTENT") == "yes"
+
+func (params *FullRequest) compileRequest() (*http.Request, error) {
+ var logBody any
+ reqBody := params.RequestBody
+ if params.Context == nil {
+ params.Context = context.Background()
+ }
+ if params.RequestJSON != nil {
+ jsonStr, err := json.Marshal(params.RequestJSON)
+ if err != nil {
+ return nil, HTTPError{
+ Message: "failed to marshal JSON",
+ WrappedError: err,
+ }
+ }
+ if params.SensitiveContent && !logSensitiveContent {
+ logBody = "<sensitive content omitted>"
+ } else {
+ logBody = params.RequestJSON
+ }
+ reqBody = bytes.NewReader(jsonStr)
+ } else if params.RequestBytes != nil {
+ logBody = fmt.Sprintf("<%d bytes>", len(params.RequestBytes))
+ reqBody = bytes.NewReader(params.RequestBytes)
+ params.RequestLength = int64(len(params.RequestBytes))
+ } else if params.RequestLength > 0 && params.RequestBody != nil {
+ logBody = fmt.Sprintf("<%d bytes>", params.RequestLength)
+ } else if params.Method != http.MethodGet && params.Method != http.MethodHead {
+ params.RequestJSON = struct{}{}
+ logBody = params.RequestJSON
+ reqBody = bytes.NewReader([]byte("{}"))
+ }
+ reqID := atomic.AddInt32(&requestID, 1)
+ ctx := params.Context
+ logger := zerolog.Ctx(ctx)
+ if logger.GetLevel() == zerolog.Disabled || logger == zerolog.DefaultContextLogger {
+ logger = params.Logger
+ }
+ ctx = logger.With().
+ Int32("req_id", reqID).
+ Logger().WithContext(ctx)
+ ctx = context.WithValue(ctx, LogBodyContextKey, logBody)
+ ctx = context.WithValue(ctx, LogRequestIDContextKey, int(reqID))
+ req, err := http.NewRequestWithContext(ctx, params.Method, params.URL, reqBody)
+ if err != nil {
+ return nil, HTTPError{
+ Message: "failed to create request",
+ WrappedError: err,
+ }
+ }
+ if params.Headers != nil {
+ req.Header = params.Headers
+ }
+ if params.RequestJSON != nil {
+ req.Header.Set("Content-Type", "application/json")
+ }
+ if params.RequestLength > 0 && params.RequestBody != nil {
+ req.ContentLength = params.RequestLength
+ }
+ return req, nil
+}
+
+// MakeFullRequest 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) MakeFullRequest(params FullRequest) ([]byte, error) {
+ if params.MaxAttempts == 0 {
+ params.MaxAttempts = 1 + cli.DefaultHTTPRetries
+ }
+ if params.Logger == nil {
+ params.Logger = &cli.Log
+ }
+ req, err := params.compileRequest()
+ if err != nil {
+ return nil, err
+ }
+ if params.Handler == nil {
+ params.Handler = cli.handleNormalResponse
+ }
+ req.Header.Set("User-Agent", cli.UserAgent)
+ if len(cli.AccessToken) > 0 {
+ req.Header.Set("Authorization", "Bearer "+cli.AccessToken)
+ }
+ return cli.executeCompiledRequest(req, params.MaxAttempts-1, 4*time.Second, params.ResponseJSON, params.Handler)
+}
+
+func (cli *Client) cliOrContextLog(ctx context.Context) *zerolog.Logger {
+ log := zerolog.Ctx(ctx)
+ if log.GetLevel() == zerolog.Disabled || log == zerolog.DefaultContextLogger {
+ return &cli.Log
+ }
+ return log
+}
+
+func (cli *Client) doRetry(req *http.Request, cause error, retries int, backoff time.Duration, responseJSON interface{}, handler ClientResponseHandler) ([]byte, error) {
+ log := zerolog.Ctx(req.Context())
+ if req.Body != nil {
+ if req.GetBody == nil {
+ log.Warn().Msg("Failed to get new body to retry request: GetBody is nil")
+ return nil, cause
+ }
+ var err error
+ req.Body, err = req.GetBody()
+ if err != nil {
+ log.Warn().Err(err).Msg("Failed to get new body to retry request")
+ return nil, cause
+ }
+ }
+ log.Warn().Err(cause).
+ Int("retry_in_seconds", int(backoff.Seconds())).
+ Msg("Request failed, retrying")
+ time.Sleep(backoff)
+ return cli.executeCompiledRequest(req, retries-1, backoff*2, responseJSON, handler)
+}
+
+func (cli *Client) readRequestBody(req *http.Request, res *http.Response) ([]byte, error) {
+ contents, err := io.ReadAll(res.Body)
+ if err != nil {
+ return nil, HTTPError{
+ Request: req,
+ Response: res,
+
+ Message: "failed to read response body",
+ WrappedError: err,
+ }
+ }
+ return contents, nil
+}
+
+func closeTemp(log *zerolog.Logger, file *os.File) {
+ _ = file.Close()
+ err := os.Remove(file.Name())
+ if err != nil {
+ log.Warn().Err(err).Str("file_name", file.Name()).Msg("Failed to remove response temp file")
+ }
+}
+
+func (cli *Client) streamResponse(req *http.Request, res *http.Response, responseJSON interface{}) ([]byte, error) {
+ log := zerolog.Ctx(req.Context())
+ file, err := os.CreateTemp("", "mautrix-response-")
+ if err != nil {
+ log.Warn().Err(err).Msg("Failed to create temporary file for streaming response")
+ _, err = cli.handleNormalResponse(req, res, responseJSON)
+ return nil, err
+ }
+ defer closeTemp(log, file)
+ if _, err = io.Copy(file, res.Body); err != nil {
+ return nil, fmt.Errorf("failed to copy response to file: %w", err)
+ } else if _, err = file.Seek(0, 0); err != nil {
+ return nil, fmt.Errorf("failed to seek to beginning of response file: %w", err)
+ } else if err = json.NewDecoder(file).Decode(responseJSON); err != nil {
+ return nil, fmt.Errorf("failed to unmarshal response body: %w", err)
+ } else {
+ return nil, nil
+ }
+}
+
+func (cli *Client) handleNormalResponse(req *http.Request, res *http.Response, responseJSON interface{}) ([]byte, error) {
+ if contents, err := cli.readRequestBody(req, res); err != nil {
+ return nil, err
+ } else if responseJSON == nil {
+ return contents, nil
+ } else if err = json.Unmarshal(contents, &responseJSON); err != nil {
+ return nil, HTTPError{
+ Request: req,
+ Response: res,
+
+ Message: "failed to unmarshal response body",
+ ResponseBody: string(contents),
+ WrappedError: err,
+ }
+ } else {
+ return contents, nil
+ }
+}
+
+func (cli *Client) handleResponseError(req *http.Request, res *http.Response) ([]byte, error) {
+ contents, err := cli.readRequestBody(req, res)
+ if err != nil {
+ return contents, err
+ }
+
+ respErr := &RespError{}
+ if _ = json.Unmarshal(contents, respErr); respErr.ErrCode == "" {
+ respErr = nil
+ }
+
+ return contents, HTTPError{
+ Request: req,
+ Response: res,
+ RespError: respErr,
+ }
+}
+
+// parseBackoffFromResponse extracts the backoff time specified in the Retry-After header if present. See
+// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After.
+func (cli *Client) parseBackoffFromResponse(req *http.Request, res *http.Response, now time.Time, fallback time.Duration) time.Duration {
+ retryAfterHeaderValue := res.Header.Get("Retry-After")
+ if retryAfterHeaderValue == "" {
+ return fallback
+ }
+
+ if t, err := time.Parse(http.TimeFormat, retryAfterHeaderValue); err == nil {
+ return t.Sub(now)
+ }
+
+ if seconds, err := strconv.Atoi(retryAfterHeaderValue); err == nil {
+ return time.Duration(seconds) * time.Second
+ }
+
+ zerolog.Ctx(req.Context()).Warn().
+ Str("retry_after", retryAfterHeaderValue).
+ Msg("Failed to parse Retry-After header value")
+
+ return fallback
+}
+
+func (cli *Client) shouldRetry(res *http.Response) bool {
+ return res.StatusCode == http.StatusBadGateway ||
+ res.StatusCode == http.StatusServiceUnavailable ||
+ res.StatusCode == http.StatusGatewayTimeout ||
+ (res.StatusCode == http.StatusTooManyRequests && !cli.IgnoreRateLimit)
+}
+
+func (cli *Client) executeCompiledRequest(req *http.Request, retries int, backoff time.Duration, responseJSON interface{}, handler ClientResponseHandler) ([]byte, error) {
+ cli.LogRequest(req)
+ startTime := time.Now()
+ res, err := cli.Client.Do(req)
+ duration := time.Now().Sub(startTime)
+ if res != nil {
+ defer res.Body.Close()
+ }
+ if err != nil {
+ if retries > 0 {
+ return cli.doRetry(req, err, retries, backoff, responseJSON, handler)
+ }
+ return nil, HTTPError{
+ Request: req,
+ Response: res,
+
+ Message: "request error",
+ WrappedError: err,
+ }
+ }
+
+ if retries > 0 && cli.shouldRetry(res) {
+ if res.StatusCode == http.StatusTooManyRequests {
+ backoff = cli.parseBackoffFromResponse(req, res, time.Now(), backoff)
+ }
+ return cli.doRetry(req, fmt.Errorf("HTTP %d", res.StatusCode), retries, backoff, responseJSON, handler)
+ }
+
+ var body []byte
+ if res.StatusCode < 200 || res.StatusCode >= 300 {
+ body, err = cli.handleResponseError(req, res)
+ cli.LogRequestDone(req, res, nil, len(body), duration)
+ } else {
+ body, err = handler(req, res, responseJSON)
+ cli.LogRequestDone(req, res, err, len(body), duration)
+ }
+ return body, err
+}
+
+// Whoami gets the user ID of the current user. See https://spec.matrix.org/v1.2/client-server-api/#get_matrixclientv3accountwhoami
+func (cli *Client) Whoami() (resp *RespWhoami, err error) {
+ urlPath := cli.BuildClientURL("v3", "account", "whoami")
+ _, err = cli.MakeRequest("GET", urlPath, nil, &resp)
+ return
+}
+
+// CreateFilter makes an HTTP request according to https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3useruseridfilter
+func (cli *Client) CreateFilter(filter *Filter) (resp *RespCreateFilter, err error) {
+ urlPath := cli.BuildClientURL("v3", "user", cli.UserID, "filter")
+ _, err = cli.MakeRequest("POST", urlPath, filter, &resp)
+ return
+}
+
+// SyncRequest makes an HTTP request according to https://spec.matrix.org/v1.2/client-server-api/#get_matrixclientv3sync
+func (cli *Client) SyncRequest(timeout int, since, filterID string, fullState bool, setPresence event.Presence, ctx context.Context) (resp *RespSync, err error) {
+ return cli.FullSyncRequest(ReqSync{
+ Timeout: timeout,
+ Since: since,
+ FilterID: filterID,
+ FullState: fullState,
+ SetPresence: setPresence,
+ Context: ctx,
+ })
+}
+
+type ReqSync struct {
+ Timeout int
+ Since string
+ FilterID string
+ FullState bool
+ SetPresence event.Presence
+
+ Context context.Context
+ StreamResponse bool
+}
+
+func (req *ReqSync) BuildQuery() map[string]string {
+ query := map[string]string{
+ "timeout": strconv.Itoa(req.Timeout),
+ }
+ if req.Since != "" {
+ query["since"] = req.Since
+ }
+ if req.FilterID != "" {
+ query["filter"] = req.FilterID
+ }
+ if req.SetPresence != "" {
+ query["set_presence"] = string(req.SetPresence)
+ }
+ if req.FullState {
+ query["full_state"] = "true"
+ }
+ return query
+}
+
+// FullSyncRequest makes an HTTP request according to https://spec.matrix.org/v1.2/client-server-api/#get_matrixclientv3sync
+func (cli *Client) FullSyncRequest(req ReqSync) (resp *RespSync, err error) {
+ urlPath := cli.BuildURLWithQuery(ClientURLPath{"v3", "sync"}, req.BuildQuery())
+ fullReq := FullRequest{
+ Method: http.MethodGet,
+ URL: urlPath,
+ ResponseJSON: &resp,
+ Context: req.Context,
+ // We don't want automatic retries for SyncRequest, the Sync() wrapper handles those.
+ MaxAttempts: 1,
+ }
+ if req.StreamResponse {
+ fullReq.Handler = cli.streamResponse
+ }
+ start := time.Now()
+ _, err = cli.MakeFullRequest(fullReq)
+ duration := time.Now().Sub(start)
+ timeout := time.Duration(req.Timeout) * time.Millisecond
+ buffer := 10 * time.Second
+ if req.Since == "" {
+ buffer = 1 * time.Minute
+ }
+ if err == nil && duration > timeout+buffer {
+ cli.cliOrContextLog(fullReq.Context).Warn().
+ Str("since", req.Since).
+ Dur("duration", duration).
+ Dur("timeout", timeout).
+ Msg("Sync request took unusually long")
+ }
+ return
+}
+
+// RegisterAvailable checks if a username is valid and available for registration on the server.
+//
+// See https://spec.matrix.org/v1.4/client-server-api/#get_matrixclientv3registeravailable for more details
+//
+// This will always return an error if the username isn't available, so checking the actual response struct is generally
+// not necessary. It is still returned for future-proofing. For a simple availability check, just check that the returned
+// error is nil. `errors.Is` can be used to find the exact reason why a username isn't available:
+//
+// _, err := cli.RegisterAvailable("cat")
+// if errors.Is(err, mautrix.MUserInUse) {
+// // Username is taken
+// } else if errors.Is(err, mautrix.MInvalidUsername) {
+// // Username is not valid
+// } else if errors.Is(err, mautrix.MExclusive) {
+// // Username is reserved for an appservice
+// } else if errors.Is(err, mautrix.MLimitExceeded) {
+// // Too many requests
+// } else if err != nil {
+// // Unknown error
+// } else {
+// // Username is available
+// }
+func (cli *Client) RegisterAvailable(username string) (resp *RespRegisterAvailable, err error) {
+ u := cli.BuildURLWithQuery(ClientURLPath{"v3", "register", "available"}, map[string]string{"username": username})
+ _, err = cli.MakeRequest(http.MethodGet, u, nil, &resp)
+ if err == nil && !resp.Available {
+ err = fmt.Errorf(`request returned OK status without "available": true`)
+ }
+ return
+}
+
+func (cli *Client) register(url string, req *ReqRegister) (resp *RespRegister, uiaResp *RespUserInteractive, err error) {
+ var bodyBytes []byte
+ bodyBytes, err = cli.MakeFullRequest(FullRequest{
+ Method: http.MethodPost,
+ URL: url,
+ RequestJSON: req,
+ SensitiveContent: len(req.Password) > 0,
+ })
+ if err != nil {
+ httpErr, ok := err.(HTTPError)
+ // if response has a 401 status, but doesn't have the errcode field, it's probably a UIA response.
+ if ok && httpErr.IsStatus(http.StatusUnauthorized) && httpErr.RespError == nil {
+ err = json.Unmarshal(bodyBytes, &uiaResp)
+ }
+ } else {
+ // body should be RespRegister
+ err = json.Unmarshal(bodyBytes, &resp)
+ }
+ return
+}
+
+// Register makes an HTTP request according to https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3register
+//
+// Registers with kind=user. For kind=guest, see RegisterGuest.
+func (cli *Client) Register(req *ReqRegister) (*RespRegister, *RespUserInteractive, error) {
+ u := cli.BuildClientURL("v3", "register")
+ return cli.register(u, req)
+}
+
+// RegisterGuest makes an HTTP request according to https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3register
+// 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(ClientURLPath{"v3", "register"}, query)
+ return cli.register(u, req)
+}
+
+// RegisterDummy performs m.login.dummy registration according to https://spec.matrix.org/v1.2/client-server-api/#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(&mautrix.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
+ } else if uia == nil {
+ return nil, errors.New("server did not return user-interactive auth flows")
+ } else if !uia.HasSingleStageFlow(AuthTypeDummy) {
+ return nil, errors.New("server does not support m.login.dummy")
+ }
+ req.Auth = BaseAuthData{Type: AuthTypeDummy, Session: uia.Session}
+ res, _, err = cli.Register(req)
+ if err != nil {
+ return nil, err
+ }
+ return res, nil
+}
+
+// GetLoginFlows fetches the login flows that the homeserver supports using https://spec.matrix.org/v1.2/client-server-api/#get_matrixclientv3login
+func (cli *Client) GetLoginFlows() (resp *RespLoginFlows, err error) {
+ urlPath := cli.BuildClientURL("v3", "login")
+ _, err = cli.MakeRequest("GET", urlPath, nil, &resp)
+ return
+}
+
+// Login a user to the homeserver according to https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3login
+func (cli *Client) Login(req *ReqLogin) (resp *RespLogin, err error) {
+ _, err = cli.MakeFullRequest(FullRequest{
+ Method: http.MethodPost,
+ URL: cli.BuildClientURL("v3", "login"),
+ RequestJSON: req,
+ ResponseJSON: &resp,
+ SensitiveContent: len(req.Password) > 0 || len(req.Token) > 0,
+ })
+ if req.StoreCredentials && err == nil {
+ cli.DeviceID = resp.DeviceID
+ cli.AccessToken = resp.AccessToken
+ cli.UserID = resp.UserID
+
+ cli.Log.Debug().
+ Str("user_id", cli.UserID.String()).
+ Str("device_id", cli.DeviceID.String()).
+ Msg("Stored credentials after login")
+ }
+ if req.StoreHomeserverURL && err == nil && resp.WellKnown != nil && len(resp.WellKnown.Homeserver.BaseURL) > 0 {
+ var urlErr error
+ cli.HomeserverURL, urlErr = url.Parse(resp.WellKnown.Homeserver.BaseURL)
+ if urlErr != nil {
+ cli.Log.Warn().
+ Err(urlErr).
+ Str("homeserver_url", resp.WellKnown.Homeserver.BaseURL).
+ Msg("Failed to parse homeserver URL in login response")
+ } else {
+ cli.Log.Debug().
+ Str("homeserver_url", cli.HomeserverURL.String()).
+ Msg("Updated homeserver URL after login")
+ }
+ }
+ return
+}
+
+// Logout the current user. See https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3logout
+// This does not clear the credentials from the client instance. See ClearCredentials() instead.
+func (cli *Client) Logout() (resp *RespLogout, err error) {
+ urlPath := cli.BuildClientURL("v3", "logout")
+ _, err = cli.MakeRequest("POST", urlPath, nil, &resp)
+ return
+}
+
+// LogoutAll logs out all the devices of the current user. See https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3logoutall
+// This does not clear the credentials from the client instance. See ClearCredentials() instead.
+func (cli *Client) LogoutAll() (resp *RespLogout, err error) {
+ urlPath := cli.BuildClientURL("v3", "logout", "all")
+ _, err = cli.MakeRequest("POST", urlPath, nil, &resp)
+ return
+}
+
+// Versions returns the list of supported Matrix versions on this homeserver. See https://spec.matrix.org/v1.2/client-server-api/#get_matrixclientversions
+func (cli *Client) Versions() (resp *RespVersions, err error) {
+ urlPath := cli.BuildClientURL("versions")
+ _, err = cli.MakeRequest("GET", urlPath, nil, &resp)
+ return
+}
+
+// Capabilities returns capabilities on this homeserver. See https://spec.matrix.org/v1.3/client-server-api/#capabilities-negotiation
+func (cli *Client) Capabilities() (resp *RespCapabilities, err error) {
+ urlPath := cli.BuildClientURL("v3", "capabilities")
+ _, err = cli.MakeRequest("GET", urlPath, nil, &resp)
+ return
+}
+
+// JoinRoom joins the client to a room ID or alias. See https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3joinroomidoralias
+//
+// 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(ClientURLPath{"v3", "join", roomIDorAlias}, map[string]string{
+ "server_name": serverName,
+ })
+ } else {
+ urlPath = cli.BuildClientURL("v3", "join", roomIDorAlias)
+ }
+ _, err = cli.MakeRequest("POST", urlPath, content, &resp)
+ if err == nil && cli.StateStore != nil {
+ cli.StateStore.SetMembership(resp.RoomID, cli.UserID, event.MembershipJoin)
+ }
+ return
+}
+
+// JoinRoomByID joins the client to a room ID. See https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3roomsroomidjoin
+//
+// Unlike JoinRoom, this method can only be used to join rooms that the server already knows about.
+// It's mostly intended for bridges and other things where it's already certain that the server is in the room.
+func (cli *Client) JoinRoomByID(roomID id.RoomID) (resp *RespJoinRoom, err error) {
+ _, err = cli.MakeRequest("POST", cli.BuildClientURL("v3", "rooms", roomID, "join"), nil, &resp)
+ if err == nil && cli.StateStore != nil {
+ cli.StateStore.SetMembership(resp.RoomID, cli.UserID, event.MembershipJoin)
+ }
+ return
+}
+
+func (cli *Client) GetProfile(mxid id.UserID) (resp *RespUserProfile, err error) {
+ urlPath := cli.BuildClientURL("v3", "profile", mxid)
+ _, err = cli.MakeRequest("GET", urlPath, nil, &resp)
+ return
+}
+
+// GetDisplayName returns the display name of the user with the specified MXID. See https://spec.matrix.org/v1.2/client-server-api/#get_matrixclientv3profileuseriddisplayname
+func (cli *Client) GetDisplayName(mxid id.UserID) (resp *RespUserDisplayName, err error) {
+ urlPath := cli.BuildClientURL("v3", "profile", mxid, "displayname")
+ _, err = cli.MakeRequest("GET", urlPath, nil, &resp)
+ return
+}
+
+// GetOwnDisplayName returns the user's display name. See https://spec.matrix.org/v1.2/client-server-api/#get_matrixclientv3profileuseriddisplayname
+func (cli *Client) GetOwnDisplayName() (resp *RespUserDisplayName, err error) {
+ return cli.GetDisplayName(cli.UserID)
+}
+
+// SetDisplayName sets the user's profile display name. See https://spec.matrix.org/v1.2/client-server-api/#put_matrixclientv3profileuseriddisplayname
+func (cli *Client) SetDisplayName(displayName string) (err error) {
+ urlPath := cli.BuildClientURL("v3", "profile", cli.UserID, "displayname")
+ s := struct {
+ DisplayName string `json:"displayname"`
+ }{displayName}
+ _, err = cli.MakeRequest("PUT", urlPath, &s, nil)
+ return
+}
+
+// GetAvatarURL gets the avatar URL of the user with the specified MXID. See https://spec.matrix.org/v1.2/client-server-api/#get_matrixclientv3profileuseridavatar_url
+func (cli *Client) GetAvatarURL(mxid id.UserID) (url id.ContentURI, err error) {
+ urlPath := cli.BuildClientURL("v3", "profile", mxid, "avatar_url")
+ s := struct {
+ AvatarURL id.ContentURI `json:"avatar_url"`
+ }{}
+
+ _, err = cli.MakeRequest("GET", urlPath, nil, &s)
+ if err != nil {
+ return
+ }
+ url = s.AvatarURL
+ return
+}
+
+// GetOwnAvatarURL gets the user's avatar URL. See https://spec.matrix.org/v1.2/client-server-api/#get_matrixclientv3profileuseridavatar_url
+func (cli *Client) GetOwnAvatarURL() (url id.ContentURI, err error) {
+ return cli.GetAvatarURL(cli.UserID)
+}
+
+// SetAvatarURL sets the user's avatar URL. See https://spec.matrix.org/v1.2/client-server-api/#put_matrixclientv3profileuseridavatar_url
+func (cli *Client) SetAvatarURL(url id.ContentURI) (err error) {
+ urlPath := cli.BuildClientURL("v3", "profile", cli.UserID, "avatar_url")
+ s := struct {
+ AvatarURL string `json:"avatar_url"`
+ }{url.String()}
+ _, err = cli.MakeRequest("PUT", urlPath, &s, nil)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// GetAccountData gets the user's account data of this type. See https://spec.matrix.org/v1.2/client-server-api/#get_matrixclientv3useruseridaccount_datatype
+func (cli *Client) GetAccountData(name string, output interface{}) (err error) {
+ urlPath := cli.BuildClientURL("v3", "user", cli.UserID, "account_data", name)
+ _, err = cli.MakeRequest("GET", urlPath, nil, output)
+ return
+}
+
+// SetAccountData sets the user's account data of this type. See https://spec.matrix.org/v1.2/client-server-api/#put_matrixclientv3useruseridaccount_datatype
+func (cli *Client) SetAccountData(name string, data interface{}) (err error) {
+ urlPath := cli.BuildClientURL("v3", "user", cli.UserID, "account_data", name)
+ _, err = cli.MakeRequest("PUT", urlPath, data, nil)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// GetRoomAccountData gets the user's account data of this type in a specific room. See https://spec.matrix.org/v1.2/client-server-api/#put_matrixclientv3useruseridaccount_datatype
+func (cli *Client) GetRoomAccountData(roomID id.RoomID, name string, output interface{}) (err error) {
+ urlPath := cli.BuildClientURL("v3", "user", cli.UserID, "rooms", roomID, "account_data", name)
+ _, err = cli.MakeRequest("GET", urlPath, nil, output)
+ return
+}
+
+// SetRoomAccountData sets the user's account data of this type in a specific room. See https://spec.matrix.org/v1.2/client-server-api/#put_matrixclientv3useruseridroomsroomidaccount_datatype
+func (cli *Client) SetRoomAccountData(roomID id.RoomID, name string, data interface{}) (err error) {
+ urlPath := cli.BuildClientURL("v3", "user", cli.UserID, "rooms", roomID, "account_data", name)
+ _, err = cli.MakeRequest("PUT", urlPath, data, nil)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+type ReqSendEvent struct {
+ Timestamp int64
+ TransactionID string
+
+ DontEncrypt bool
+
+ MeowEventID id.EventID
+}
+
+// SendMessageEvent sends a message event into a room. See https://spec.matrix.org/v1.2/client-server-api/#put_matrixclientv3roomsroomidsendeventtypetxnid
+// contentJSON should be a pointer to something that can be encoded as JSON using json.Marshal.
+func (cli *Client) SendMessageEvent(roomID id.RoomID, eventType event.Type, contentJSON interface{}, extra ...ReqSendEvent) (resp *RespSendEvent, err error) {
+ var req ReqSendEvent
+ if len(extra) > 0 {
+ req = extra[0]
+ }
+
+ var txnID string
+ if len(req.TransactionID) > 0 {
+ txnID = req.TransactionID
+ } else {
+ txnID = cli.TxnID()
+ }
+
+ queryParams := map[string]string{}
+ if req.Timestamp > 0 {
+ queryParams["ts"] = strconv.FormatInt(req.Timestamp, 10)
+ }
+ if req.MeowEventID != "" {
+ queryParams["fi.mau.event_id"] = req.MeowEventID.String()
+ }
+
+ if !req.DontEncrypt && cli.Crypto != nil && eventType != event.EventReaction && eventType != event.EventEncrypted && cli.StateStore.IsEncrypted(roomID) {
+ contentJSON, err = cli.Crypto.Encrypt(roomID, eventType, contentJSON)
+ if err != nil {
+ err = fmt.Errorf("failed to encrypt event: %w", err)
+ return
+ }
+ eventType = event.EventEncrypted
+ }
+
+ urlData := ClientURLPath{"v3", "rooms", roomID, "send", eventType.String(), txnID}
+ urlPath := cli.BuildURLWithQuery(urlData, queryParams)
+ _, err = cli.MakeRequest("PUT", urlPath, contentJSON, &resp)
+ return
+}
+
+// SendStateEvent sends a state event into a room. See https://spec.matrix.org/v1.2/client-server-api/#put_matrixclientv3roomsroomidstateeventtypestatekey
+// contentJSON should be a pointer to something that can be encoded as JSON using json.Marshal.
+func (cli *Client) SendStateEvent(roomID id.RoomID, eventType event.Type, stateKey string, contentJSON interface{}) (resp *RespSendEvent, err error) {
+ urlPath := cli.BuildClientURL("v3", "rooms", roomID, "state", eventType.String(), stateKey)
+ _, err = cli.MakeRequest("PUT", urlPath, contentJSON, &resp)
+ if err == nil && cli.StateStore != nil {
+ cli.updateStoreWithOutgoingEvent(roomID, eventType, stateKey, contentJSON)
+ }
+ return
+}
+
+// SendMassagedStateEvent sends a state event into a room with a custom timestamp. See https://spec.matrix.org/v1.2/client-server-api/#put_matrixclientv3roomsroomidstateeventtypestatekey
+// contentJSON should be a pointer to something that can be encoded as JSON using json.Marshal.
+func (cli *Client) SendMassagedStateEvent(roomID id.RoomID, eventType event.Type, stateKey string, contentJSON interface{}, ts int64) (resp *RespSendEvent, err error) {
+ urlPath := cli.BuildURLWithQuery(ClientURLPath{"v3", "rooms", roomID, "state", eventType.String(), stateKey}, map[string]string{
+ "ts": strconv.FormatInt(ts, 10),
+ })
+ _, err = cli.MakeRequest("PUT", urlPath, contentJSON, &resp)
+ if err == nil && cli.StateStore != nil {
+ cli.updateStoreWithOutgoingEvent(roomID, eventType, stateKey, contentJSON)
+ }
+ return
+}
+
+// SendText sends an m.room.message event into the given room with a msgtype of m.text
+// See https://spec.matrix.org/v1.2/client-server-api/#mtext
+func (cli *Client) SendText(roomID id.RoomID, text string) (*RespSendEvent, error) {
+ return cli.SendMessageEvent(roomID, event.EventMessage, &event.MessageEventContent{
+ MsgType: event.MsgText,
+ Body: text,
+ })
+}
+
+// SendNotice sends an m.room.message event into the given room with a msgtype of m.notice
+// See https://spec.matrix.org/v1.2/client-server-api/#mnotice
+func (cli *Client) SendNotice(roomID id.RoomID, text string) (*RespSendEvent, error) {
+ return cli.SendMessageEvent(roomID, event.EventMessage, &event.MessageEventContent{
+ MsgType: event.MsgNotice,
+ Body: text,
+ })
+}
+
+func (cli *Client) SendReaction(roomID id.RoomID, eventID id.EventID, reaction string) (*RespSendEvent, error) {
+ return cli.SendMessageEvent(roomID, event.EventReaction, &event.ReactionEventContent{
+ RelatesTo: event.RelatesTo{
+ EventID: eventID,
+ Type: event.RelAnnotation,
+ Key: reaction,
+ },
+ })
+}
+
+// RedactEvent redacts the given event. See https://spec.matrix.org/v1.2/client-server-api/#put_matrixclientv3roomsroomidredacteventidtxnid
+func (cli *Client) RedactEvent(roomID id.RoomID, eventID id.EventID, extra ...ReqRedact) (resp *RespSendEvent, err error) {
+ req := ReqRedact{}
+ if len(extra) > 0 {
+ req = extra[0]
+ }
+ if req.Extra == nil {
+ req.Extra = make(map[string]interface{})
+ }
+ if len(req.Reason) > 0 {
+ req.Extra["reason"] = req.Reason
+ }
+ var txnID string
+ if len(req.TxnID) > 0 {
+ txnID = req.TxnID
+ } else {
+ txnID = cli.TxnID()
+ }
+ urlPath := cli.BuildClientURL("v3", "rooms", roomID, "redact", eventID, txnID)
+ _, err = cli.MakeRequest("PUT", urlPath, req.Extra, &resp)
+ return
+}
+
+// CreateRoom creates a new Matrix room. See https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3createroom
+//
+// resp, err := cli.CreateRoom(&mautrix.ReqCreateRoom{
+// Preset: "public_chat",
+// })
+// fmt.Println("Room:", resp.RoomID)
+func (cli *Client) CreateRoom(req *ReqCreateRoom) (resp *RespCreateRoom, err error) {
+ urlPath := cli.BuildClientURL("v3", "createRoom")
+ _, err = cli.MakeRequest("POST", urlPath, req, &resp)
+ if err == nil && cli.StateStore != nil {
+ cli.StateStore.SetMembership(resp.RoomID, cli.UserID, event.MembershipJoin)
+ for _, evt := range req.InitialState {
+ UpdateStateStore(cli.StateStore, evt)
+ }
+ inviteMembership := event.MembershipInvite
+ if req.BeeperAutoJoinInvites {
+ inviteMembership = event.MembershipJoin
+ }
+ for _, invitee := range req.Invite {
+ cli.StateStore.SetMembership(resp.RoomID, invitee, inviteMembership)
+ }
+ }
+ return
+}
+
+// LeaveRoom leaves the given room. See https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3roomsroomidleave
+func (cli *Client) LeaveRoom(roomID id.RoomID, optionalReq ...*ReqLeave) (resp *RespLeaveRoom, err error) {
+ req := &ReqLeave{}
+ if len(optionalReq) == 1 {
+ req = optionalReq[0]
+ } else if len(optionalReq) > 1 {
+ panic("invalid number of arguments to LeaveRoom")
+ }
+ u := cli.BuildClientURL("v3", "rooms", roomID, "leave")
+ _, err = cli.MakeRequest("POST", u, req, &resp)
+ if err == nil && cli.StateStore != nil {
+ cli.StateStore.SetMembership(roomID, cli.UserID, event.MembershipLeave)
+ }
+ return
+}
+
+// ForgetRoom forgets a room entirely. See https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3roomsroomidforget
+func (cli *Client) ForgetRoom(roomID id.RoomID) (resp *RespForgetRoom, err error) {
+ u := cli.BuildClientURL("v3", "rooms", roomID, "forget")
+ _, err = cli.MakeRequest("POST", u, struct{}{}, &resp)
+ return
+}
+
+// InviteUser invites a user to a room. See https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3roomsroomidinvite
+func (cli *Client) InviteUser(roomID id.RoomID, req *ReqInviteUser) (resp *RespInviteUser, err error) {
+ u := cli.BuildClientURL("v3", "rooms", roomID, "invite")
+ _, err = cli.MakeRequest("POST", u, req, &resp)
+ if err == nil && cli.StateStore != nil {
+ cli.StateStore.SetMembership(roomID, req.UserID, event.MembershipInvite)
+ }
+ return
+}
+
+// InviteUserByThirdParty invites a third-party identifier to a room. See https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3roomsroomidinvite-1
+func (cli *Client) InviteUserByThirdParty(roomID id.RoomID, req *ReqInvite3PID) (resp *RespInviteUser, err error) {
+ u := cli.BuildClientURL("v3", "rooms", roomID, "invite")
+ _, err = cli.MakeRequest("POST", u, req, &resp)
+ return
+}
+
+// KickUser kicks a user from a room. See https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3roomsroomidkick
+func (cli *Client) KickUser(roomID id.RoomID, req *ReqKickUser) (resp *RespKickUser, err error) {
+ u := cli.BuildClientURL("v3", "rooms", roomID, "kick")
+ _, err = cli.MakeRequest("POST", u, req, &resp)
+ if err == nil && cli.StateStore != nil {
+ cli.StateStore.SetMembership(roomID, req.UserID, event.MembershipLeave)
+ }
+ return
+}
+
+// BanUser bans a user from a room. See https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3roomsroomidban
+func (cli *Client) BanUser(roomID id.RoomID, req *ReqBanUser) (resp *RespBanUser, err error) {
+ u := cli.BuildClientURL("v3", "rooms", roomID, "ban")
+ _, err = cli.MakeRequest("POST", u, req, &resp)
+ if err == nil && cli.StateStore != nil {
+ cli.StateStore.SetMembership(roomID, req.UserID, event.MembershipBan)
+ }
+ return
+}
+
+// UnbanUser unbans a user from a room. See https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3roomsroomidunban
+func (cli *Client) UnbanUser(roomID id.RoomID, req *ReqUnbanUser) (resp *RespUnbanUser, err error) {
+ u := cli.BuildClientURL("v3", "rooms", roomID, "unban")
+ _, err = cli.MakeRequest("POST", u, req, &resp)
+ if err == nil && cli.StateStore != nil {
+ cli.StateStore.SetMembership(roomID, req.UserID, event.MembershipLeave)
+ }
+ return
+}
+
+// UserTyping sets the typing status of the user. See https://spec.matrix.org/v1.2/client-server-api/#put_matrixclientv3roomsroomidtypinguserid
+func (cli *Client) UserTyping(roomID id.RoomID, typing bool, timeout time.Duration) (resp *RespTyping, err error) {
+ req := ReqTyping{Typing: typing, Timeout: timeout.Milliseconds()}
+ u := cli.BuildClientURL("v3", "rooms", roomID, "typing", cli.UserID)
+ _, err = cli.MakeRequest("PUT", u, req, &resp)
+ return
+}
+
+// GetPresence gets the presence of the user with the specified MXID. See https://spec.matrix.org/v1.2/client-server-api/#get_matrixclientv3presenceuseridstatus
+func (cli *Client) GetPresence(userID id.UserID) (resp *RespPresence, err error) {
+ resp = new(RespPresence)
+ u := cli.BuildClientURL("v3", "presence", userID, "status")
+ _, err = cli.MakeRequest("GET", u, nil, resp)
+ return
+}
+
+// GetOwnPresence gets the user's presence. See https://spec.matrix.org/v1.2/client-server-api/#get_matrixclientv3presenceuseridstatus
+func (cli *Client) GetOwnPresence() (resp *RespPresence, err error) {
+ return cli.GetPresence(cli.UserID)
+}
+
+func (cli *Client) SetPresence(status event.Presence) (err error) {
+ req := ReqPresence{Presence: status}
+ u := cli.BuildClientURL("v3", "presence", cli.UserID, "status")
+ _, err = cli.MakeRequest("PUT", u, req, nil)
+ return
+}
+
+func (cli *Client) updateStoreWithOutgoingEvent(roomID id.RoomID, eventType event.Type, stateKey string, contentJSON interface{}) {
+ if cli.StateStore == nil {
+ return
+ }
+ fakeEvt := &event.Event{
+ StateKey: &stateKey,
+ Type: eventType,
+ RoomID: roomID,
+ }
+ var err error
+ fakeEvt.Content.VeryRaw, err = json.Marshal(contentJSON)
+ if err != nil {
+ cli.Log.Warn().Err(err).Msg("Failed to marshal state event content to update state store")
+ return
+ }
+ err = json.Unmarshal(fakeEvt.Content.VeryRaw, &fakeEvt.Content.Raw)
+ if err != nil {
+ cli.Log.Warn().Err(err).Msg("Failed to unmarshal state event content to update state store")
+ return
+ }
+ err = fakeEvt.Content.ParseRaw(fakeEvt.Type)
+ if err != nil {
+ cli.Log.Warn().Err(err).Msg("Failed to parse state event content to update state store")
+ return
+ }
+ UpdateStateStore(cli.StateStore, fakeEvt)
+}
+
+// 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 https://spec.matrix.org/v1.2/client-server-api/#get_matrixclientv3roomsroomidstateeventtypestatekey
+func (cli *Client) StateEvent(roomID id.RoomID, eventType event.Type, stateKey string, outContent interface{}) (err error) {
+ u := cli.BuildClientURL("v3", "rooms", roomID, "state", eventType.String(), stateKey)
+ _, err = cli.MakeRequest("GET", u, nil, outContent)
+ if err == nil && cli.StateStore != nil {
+ cli.updateStoreWithOutgoingEvent(roomID, eventType, stateKey, outContent)
+ }
+ return
+}
+
+// parseRoomStateArray parses a JSON array as a stream and stores the events inside it in a room state map.
+func parseRoomStateArray(_ *http.Request, res *http.Response, responseJSON interface{}) ([]byte, error) {
+ response := make(RoomStateMap)
+ responsePtr := responseJSON.(*map[event.Type]map[string]*event.Event)
+ *responsePtr = response
+ dec := json.NewDecoder(res.Body)
+
+ arrayStart, err := dec.Token()
+ if err != nil {
+ return nil, err
+ } else if arrayStart != json.Delim('[') {
+ return nil, fmt.Errorf("expected array start, got %+v", arrayStart)
+ }
+
+ for i := 1; dec.More(); i++ {
+ var evt *event.Event
+ err = dec.Decode(&evt)
+ if err != nil {
+ return nil, fmt.Errorf("failed to parse state array item #%d: %v", i, err)
+ }
+ evt.Type.Class = event.StateEventType
+ _ = evt.Content.ParseRaw(evt.Type)
+ subMap, ok := response[evt.Type]
+ if !ok {
+ subMap = make(map[string]*event.Event)
+ response[evt.Type] = subMap
+ }
+ subMap[*evt.StateKey] = evt
+ }
+
+ arrayEnd, err := dec.Token()
+ if err != nil {
+ return nil, err
+ } else if arrayEnd != json.Delim(']') {
+ return nil, fmt.Errorf("expected array end, got %+v", arrayStart)
+ }
+ return nil, nil
+}
+
+// State gets all state in a room.
+// See https://spec.matrix.org/v1.2/client-server-api/#get_matrixclientv3roomsroomidstate
+func (cli *Client) State(roomID id.RoomID) (stateMap RoomStateMap, err error) {
+ _, err = cli.MakeFullRequest(FullRequest{
+ Method: http.MethodGet,
+ URL: cli.BuildClientURL("v3", "rooms", roomID, "state"),
+ ResponseJSON: &stateMap,
+ Handler: parseRoomStateArray,
+ })
+ if err == nil && cli.StateStore != nil {
+ for _, evts := range stateMap {
+ for _, evt := range evts {
+ UpdateStateStore(cli.StateStore, evt)
+ }
+ }
+ }
+ return
+}
+
+// GetMediaConfig fetches the configuration of the content repository, such as upload limitations.
+func (cli *Client) GetMediaConfig() (resp *RespMediaConfig, err error) {
+ u := cli.BuildURL(MediaURLPath{"v3", "config"})
+ _, err = cli.MakeRequest("GET", u, nil, &resp)
+ 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.Upload(res.Body, res.Header.Get("Content-Type"), res.ContentLength)
+}
+
+func (cli *Client) GetDownloadURL(mxcURL id.ContentURI) string {
+ return cli.BuildURLWithQuery(MediaURLPath{"v3", "download", mxcURL.Homeserver, mxcURL.FileID}, map[string]string{"allow_redirect": "true"})
+}
+
+func (cli *Client) Download(mxcURL id.ContentURI) (io.ReadCloser, error) {
+ return cli.DownloadContext(context.Background(), mxcURL)
+}
+
+func (cli *Client) DownloadContext(ctx context.Context, mxcURL id.ContentURI) (io.ReadCloser, error) {
+ _, resp, err := cli.downloadContext(ctx, mxcURL)
+ return resp.Body, err
+}
+
+func (cli *Client) downloadContext(ctx context.Context, mxcURL id.ContentURI) (*http.Request, *http.Response, error) {
+ ctxLog := zerolog.Ctx(ctx)
+ if ctxLog.GetLevel() == zerolog.Disabled || ctxLog == zerolog.DefaultContextLogger {
+ ctx = cli.Log.WithContext(ctx)
+ }
+ req, err := http.NewRequestWithContext(ctx, http.MethodGet, cli.GetDownloadURL(mxcURL), nil)
+ if err != nil {
+ return req, nil, err
+ }
+ req.Header.Set("User-Agent", cli.UserAgent+" (media downloader)")
+ cli.LogRequest(req)
+ if resp, err := cli.Client.Do(req); err != nil {
+ return req, nil, err
+ } else {
+ return req, resp, nil
+ }
+}
+
+func (cli *Client) DownloadBytes(mxcURL id.ContentURI) ([]byte, error) {
+ return cli.DownloadBytesContext(context.Background(), mxcURL)
+}
+
+func (cli *Client) DownloadBytesContext(ctx context.Context, mxcURL id.ContentURI) ([]byte, error) {
+ req, resp, err := cli.downloadContext(ctx, mxcURL)
+ if err != nil {
+ return nil, err
+ }
+ defer resp.Body.Close()
+ if resp.StatusCode >= 300 || resp.StatusCode < 200 {
+ respErr := &RespError{}
+ if _ = json.NewDecoder(resp.Body).Decode(respErr); respErr.ErrCode == "" {
+ respErr = nil
+ }
+ return nil, HTTPError{Request: req, Response: resp, RespError: respErr}
+ }
+ return io.ReadAll(resp.Body)
+}
+
+// UnstableCreateMXC creates a blank Matrix content URI to allow uploading the content asynchronously later.
+// See https://github.com/matrix-org/matrix-spec-proposals/pull/2246
+func (cli *Client) UnstableCreateMXC() (*RespCreateMXC, error) {
+ u, _ := url.Parse(cli.BuildURL(MediaURLPath{"unstable", "fi.mau.msc2246", "create"}))
+ var m RespCreateMXC
+ _, err := cli.MakeFullRequest(FullRequest{
+ Method: http.MethodPost,
+ URL: u.String(),
+ ResponseJSON: &m,
+ })
+ return &m, err
+}
+
+// UnstableUploadAsync creates a blank content URI with UnstableCreateMXC, starts uploading the data in the background
+// and returns the created MXC immediately. See https://github.com/matrix-org/matrix-spec-proposals/pull/2246 for more info.
+func (cli *Client) UnstableUploadAsync(req ReqUploadMedia) (*RespCreateMXC, error) {
+ resp, err := cli.UnstableCreateMXC()
+ if err != nil {
+ return nil, err
+ }
+ req.UnstableMXC = resp.ContentURI
+ req.UploadURL = resp.UploadURL
+ go func() {
+ _, err = cli.UploadMedia(req)
+ if err != nil {
+ cli.Log.Error().Str("mxc", req.UnstableMXC.String()).Err(err).Msg("Async upload of media failed")
+ }
+ }()
+ return resp, nil
+}
+
+func (cli *Client) UploadBytes(data []byte, contentType string) (*RespMediaUpload, error) {
+ return cli.UploadBytesWithName(data, contentType, "")
+}
+
+func (cli *Client) UploadBytesWithName(data []byte, contentType, fileName string) (*RespMediaUpload, error) {
+ return cli.UploadMedia(ReqUploadMedia{
+ ContentBytes: data,
+ ContentType: contentType,
+ FileName: fileName,
+ })
+}
+
+// Upload uploads the given data to the content repository and returns an MXC URI.
+//
+// Deprecated: UploadMedia should be used instead.
+func (cli *Client) Upload(content io.Reader, contentType string, contentLength int64) (*RespMediaUpload, error) {
+ return cli.UploadMedia(ReqUploadMedia{
+ Content: content,
+ ContentLength: contentLength,
+ ContentType: contentType,
+ })
+}
+
+type ReqUploadMedia struct {
+ ContentBytes []byte
+ Content io.Reader
+ ContentLength int64
+ ContentType string
+ FileName string
+
+ // UnstableMXC specifies an existing MXC URI which doesn't have content yet to upload into.
+ // See https://github.com/matrix-org/matrix-spec-proposals/pull/2246 for more info.
+ UnstableMXC id.ContentURI
+
+ // UploadURL specifies the URL to upload the content to (MSC3870)
+ // see https://github.com/matrix-org/matrix-spec-proposals/pull/3870 for more info
+ UploadURL string
+}
+
+func (cli *Client) tryUploadMediaToURL(url, contentType string, content io.Reader) (*http.Response, error) {
+ cli.Log.Debug().Str("url", url).Msg("Uploading media to external URL")
+ req, err := http.NewRequest(http.MethodPut, url, content)
+ if err != nil {
+ return nil, err
+ }
+ req.Header.Set("Content-Type", contentType)
+ req.Header.Set("User-Agent", cli.UserAgent+" (external media uploader)")
+
+ return http.DefaultClient.Do(req)
+}
+
+func (cli *Client) uploadMediaToURL(data ReqUploadMedia) (*RespMediaUpload, error) {
+ retries := cli.DefaultHTTPRetries
+ if data.ContentBytes == nil {
+ // Can't retry with a reader
+ retries = 0
+ }
+ for {
+ reader := data.Content
+ if reader == nil {
+ reader = bytes.NewReader(data.ContentBytes)
+ } else {
+ data.Content = nil
+ }
+ resp, err := cli.tryUploadMediaToURL(data.UploadURL, data.ContentType, reader)
+ if err == nil {
+ if resp.StatusCode >= 200 && resp.StatusCode < 300 {
+ // Everything is fine
+ break
+ }
+ err = fmt.Errorf("HTTP %d", resp.StatusCode)
+ }
+ if retries <= 0 {
+ cli.Log.Warn().Str("url", data.UploadURL).Err(err).Msg("Error uploading media to external URL, not retrying")
+ return nil, err
+ }
+ cli.Log.Warn().Str("url", data.UploadURL).Err(err).Msg("Error uploading media to external URL, retrying")
+ retries--
+ }
+
+ query := map[string]string{}
+ if len(data.FileName) > 0 {
+ query["filename"] = data.FileName
+ }
+
+ notifyURL := cli.BuildURLWithQuery(MediaURLPath{"unstable", "fi.mau.msc2246", "upload", data.UnstableMXC.Homeserver, data.UnstableMXC.FileID, "complete"}, query)
+
+ var m *RespMediaUpload
+ _, err := cli.MakeFullRequest(FullRequest{
+ Method: http.MethodPost,
+ URL: notifyURL,
+ ResponseJSON: m,
+ })
+ if err != nil {
+ return nil, err
+ }
+
+ return m, nil
+}
+
+// UploadMedia uploads the given data to the content repository and returns an MXC URI.
+// See https://spec.matrix.org/v1.2/client-server-api/#post_matrixmediav3upload
+func (cli *Client) UploadMedia(data ReqUploadMedia) (*RespMediaUpload, error) {
+ if data.UploadURL != "" {
+ return cli.uploadMediaToURL(data)
+ }
+ u, _ := url.Parse(cli.BuildURL(MediaURLPath{"v3", "upload"}))
+ method := http.MethodPost
+ if !data.UnstableMXC.IsEmpty() {
+ u, _ = url.Parse(cli.BuildURL(MediaURLPath{"unstable", "fi.mau.msc2246", "upload", data.UnstableMXC.Homeserver, data.UnstableMXC.FileID}))
+ method = http.MethodPut
+ }
+ if len(data.FileName) > 0 {
+ q := u.Query()
+ q.Set("filename", data.FileName)
+ u.RawQuery = q.Encode()
+ }
+
+ var headers http.Header
+ if len(data.ContentType) > 0 {
+ headers = http.Header{"Content-Type": []string{data.ContentType}}
+ }
+
+ var m RespMediaUpload
+ _, err := cli.MakeFullRequest(FullRequest{
+ Method: method,
+ URL: u.String(),
+ Headers: headers,
+ RequestBytes: data.ContentBytes,
+ RequestBody: data.Content,
+ RequestLength: data.ContentLength,
+ ResponseJSON: &m,
+ })
+ return &m, err
+}
+
+// GetURLPreview asks the homeserver to fetch a preview for a given URL.
+//
+// See https://spec.matrix.org/v1.2/client-server-api/#get_matrixmediav3preview_url
+func (cli *Client) GetURLPreview(url string) (*RespPreviewURL, error) {
+ reqURL := cli.BuildURLWithQuery(MediaURLPath{"v3", "preview_url"}, map[string]string{
+ "url": url,
+ })
+ var output RespPreviewURL
+ _, err := cli.MakeRequest(http.MethodGet, reqURL, nil, &output)
+ return &output, err
+}
+
+// JoinedMembers returns a map of joined room members. See https://spec.matrix.org/v1.2/client-server-api/#get_matrixclientv3roomsroomidjoined_members
+//
+// 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 id.RoomID) (resp *RespJoinedMembers, err error) {
+ u := cli.BuildClientURL("v3", "rooms", roomID, "joined_members")
+ _, err = cli.MakeRequest("GET", u, nil, &resp)
+ if err == nil && cli.StateStore != nil {
+ for userID, member := range resp.Joined {
+ cli.StateStore.SetMember(roomID, userID, &event.MemberEventContent{
+ Membership: event.MembershipJoin,
+ AvatarURL: id.ContentURIString(member.AvatarURL),
+ Displayname: member.DisplayName,
+ })
+ }
+ }
+ return
+}
+
+func (cli *Client) Members(roomID id.RoomID, req ...ReqMembers) (resp *RespMembers, err error) {
+ var extra ReqMembers
+ if len(req) > 0 {
+ extra = req[0]
+ }
+ query := map[string]string{}
+ if len(extra.At) > 0 {
+ query["at"] = extra.At
+ }
+ if len(extra.Membership) > 0 {
+ query["membership"] = string(extra.Membership)
+ }
+ if len(extra.NotMembership) > 0 {
+ query["not_membership"] = string(extra.NotMembership)
+ }
+ u := cli.BuildURLWithQuery(ClientURLPath{"v3", "rooms", roomID, "members"}, query)
+ _, err = cli.MakeRequest("GET", u, nil, &resp)
+ if err == nil && cli.StateStore != nil {
+ for _, evt := range resp.Chunk {
+ UpdateStateStore(cli.StateStore, evt)
+ }
+ }
+ return
+}
+
+// JoinedRooms returns a list of rooms which the client is joined to. See https://spec.matrix.org/v1.2/client-server-api/#get_matrixclientv3joined_rooms
+//
+// 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.BuildClientURL("v3", "joined_rooms")
+ _, err = cli.MakeRequest("GET", u, nil, &resp)
+ return
+}
+
+// Hierarchy returns a list of rooms that are in the room's hierarchy. See https://spec.matrix.org/v1.4/client-server-api/#get_matrixclientv1roomsroomidhierarchy
+//
+// The hierarchy API is provided to walk the space tree and discover the rooms with their aesthetic details. works in a depth-first manner:
+// when it encounters another space as a child it recurses into that space before returning non-space children.
+//
+// The second function parameter specifies query parameters to limit the response. No query parameters will be added if it's nil.
+func (cli *Client) Hierarchy(roomID id.RoomID, req *ReqHierarchy) (resp *RespHierarchy, err error) {
+ urlPath := cli.BuildURLWithQuery(ClientURLPath{"v1", "rooms", roomID, "hierarchy"}, req.Query())
+ _, err = cli.MakeRequest(http.MethodGet, urlPath, 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://spec.matrix.org/v1.2/client-server-api/#get_matrixclientv3roomsroomidmessages
+func (cli *Client) Messages(roomID id.RoomID, from, to string, dir Direction, filter *FilterPart, limit int) (resp *RespMessages, err error) {
+ query := map[string]string{
+ "from": from,
+ "dir": string(dir),
+ }
+ if filter != nil {
+ filterJSON, err := json.Marshal(filter)
+ if err != nil {
+ return nil, err
+ }
+ query["filter"] = string(filterJSON)
+ }
+ if to != "" {
+ query["to"] = to
+ }
+ if limit != 0 {
+ query["limit"] = strconv.Itoa(limit)
+ }
+
+ urlPath := cli.BuildURLWithQuery(ClientURLPath{"v3", "rooms", roomID, "messages"}, query)
+ _, err = cli.MakeRequest("GET", urlPath, nil, &resp)
+ return
+}
+
+// TimestampToEvent finds the ID of the event closest to the given timestamp.
+//
+// See https://spec.matrix.org/v1.6/client-server-api/#get_matrixclientv1roomsroomidtimestamp_to_event
+func (cli *Client) TimestampToEvent(roomID id.RoomID, timestamp time.Time, dir Direction) (resp *RespTimestampToEvent, err error) {
+ query := map[string]string{
+ "ts": strconv.FormatInt(timestamp.UnixMilli(), 10),
+ "dir": string(dir),
+ }
+ urlPath := cli.BuildURLWithQuery(ClientURLPath{"v1", "rooms", roomID, "timestamp_to_event"}, query)
+ _, err = cli.MakeRequest(http.MethodGet, urlPath, nil, &resp)
+ return
+}
+
+// Context returns a number of events that happened just before and after the
+// specified event. It use pagination query parameters to paginate history in
+// the room.
+// See https://spec.matrix.org/v1.2/client-server-api/#get_matrixclientv3roomsroomidcontexteventid
+func (cli *Client) Context(roomID id.RoomID, eventID id.EventID, filter *FilterPart, limit int) (resp *RespContext, err error) {
+ query := map[string]string{}
+ if filter != nil {
+ filterJSON, err := json.Marshal(filter)
+ if err != nil {
+ return nil, err
+ }
+ query["filter"] = string(filterJSON)
+ }
+ if limit != 0 {
+ query["limit"] = strconv.Itoa(limit)
+ }
+
+ urlPath := cli.BuildURLWithQuery(ClientURLPath{"v3", "rooms", roomID, "context", eventID}, query)
+ _, err = cli.MakeRequest("GET", urlPath, nil, &resp)
+ return
+}
+
+func (cli *Client) GetEvent(roomID id.RoomID, eventID id.EventID) (resp *event.Event, err error) {
+ urlPath := cli.BuildClientURL("v3", "rooms", roomID, "event", eventID)
+ _, err = cli.MakeRequest("GET", urlPath, nil, &resp)
+ return
+}
+
+func (cli *Client) MarkRead(roomID id.RoomID, eventID id.EventID) (err error) {
+ return cli.SendReceipt(roomID, eventID, event.ReceiptTypeRead, nil)
+}
+
+// MarkReadWithContent sends a read receipt including custom data.
+//
+// Deprecated: Use SendReceipt instead.
+func (cli *Client) MarkReadWithContent(roomID id.RoomID, eventID id.EventID, content interface{}) (err error) {
+ return cli.SendReceipt(roomID, eventID, event.ReceiptTypeRead, content)
+}
+
+// SendReceipt sends a receipt, usually specifically a read receipt.
+//
+// Passing nil as the content is safe, the library will automatically replace it with an empty JSON object.
+// To mark a message in a specific thread as read, use pass a ReqSendReceipt as the content.
+func (cli *Client) SendReceipt(roomID id.RoomID, eventID id.EventID, receiptType event.ReceiptType, content interface{}) (err error) {
+ urlPath := cli.BuildClientURL("v3", "rooms", roomID, "receipt", receiptType, eventID)
+ _, err = cli.MakeRequest("POST", urlPath, content, nil)
+ return
+}
+
+func (cli *Client) SetReadMarkers(roomID id.RoomID, content interface{}) (err error) {
+ urlPath := cli.BuildClientURL("v3", "rooms", roomID, "read_markers")
+ _, err = cli.MakeRequest("POST", urlPath, content, nil)
+ return
+}
+
+func (cli *Client) AddTag(roomID id.RoomID, tag string, order float64) error {
+ var tagData event.Tag
+ if order == order {
+ tagData.Order = json.Number(strconv.FormatFloat(order, 'e', -1, 64))
+ }
+ return cli.AddTagWithCustomData(roomID, tag, tagData)
+}
+
+func (cli *Client) AddTagWithCustomData(roomID id.RoomID, tag string, data interface{}) (err error) {
+ urlPath := cli.BuildClientURL("v3", "user", cli.UserID, "rooms", roomID, "tags", tag)
+ _, err = cli.MakeRequest("PUT", urlPath, data, nil)
+ return
+}
+
+func (cli *Client) GetTags(roomID id.RoomID) (tags event.TagEventContent, err error) {
+ err = cli.GetTagsWithCustomData(roomID, &tags)
+ return
+}
+
+func (cli *Client) GetTagsWithCustomData(roomID id.RoomID, resp interface{}) (err error) {
+ urlPath := cli.BuildClientURL("v3", "user", cli.UserID, "rooms", roomID, "tags")
+ _, err = cli.MakeRequest("GET", urlPath, nil, &resp)
+ return
+}
+
+func (cli *Client) RemoveTag(roomID id.RoomID, tag string) (err error) {
+ urlPath := cli.BuildClientURL("v3", "user", cli.UserID, "rooms", roomID, "tags", tag)
+ _, err = cli.MakeRequest("DELETE", urlPath, nil, nil)
+ return
+}
+
+// Deprecated: Synapse may not handle setting m.tag directly properly, so you should use the Add/RemoveTag methods instead.
+func (cli *Client) SetTags(roomID id.RoomID, tags event.Tags) (err error) {
+ return cli.SetRoomAccountData(roomID, "m.tag", map[string]event.Tags{
+ "tags": tags,
+ })
+}
+
+// TurnServer returns turn server details and credentials for the client to use when initiating calls.
+// See https://spec.matrix.org/v1.2/client-server-api/#get_matrixclientv3voipturnserver
+func (cli *Client) TurnServer() (resp *RespTurnServer, err error) {
+ urlPath := cli.BuildClientURL("v3", "voip", "turnServer")
+ _, err = cli.MakeRequest("GET", urlPath, nil, &resp)
+ return
+}
+
+func (cli *Client) CreateAlias(alias id.RoomAlias, roomID id.RoomID) (resp *RespAliasCreate, err error) {
+ urlPath := cli.BuildClientURL("v3", "directory", "room", alias)
+ _, err = cli.MakeRequest("PUT", urlPath, &ReqAliasCreate{RoomID: roomID}, &resp)
+ return
+}
+
+func (cli *Client) ResolveAlias(alias id.RoomAlias) (resp *RespAliasResolve, err error) {
+ urlPath := cli.BuildClientURL("v3", "directory", "room", alias)
+ _, err = cli.MakeRequest("GET", urlPath, nil, &resp)
+ return
+}
+
+func (cli *Client) DeleteAlias(alias id.RoomAlias) (resp *RespAliasDelete, err error) {
+ urlPath := cli.BuildClientURL("v3", "directory", "room", alias)
+ _, err = cli.MakeRequest("DELETE", urlPath, nil, &resp)
+ return
+}
+
+func (cli *Client) GetAliases(roomID id.RoomID) (resp *RespAliasList, err error) {
+ urlPath := cli.BuildClientURL("v3", "rooms", roomID, "aliases")
+ _, err = cli.MakeRequest("GET", urlPath, nil, &resp)
+ return
+}
+
+func (cli *Client) UploadKeys(req *ReqUploadKeys) (resp *RespUploadKeys, err error) {
+ urlPath := cli.BuildClientURL("v3", "keys", "upload")
+ _, err = cli.MakeRequest("POST", urlPath, req, &resp)
+ return
+}
+
+func (cli *Client) QueryKeys(req *ReqQueryKeys) (resp *RespQueryKeys, err error) {
+ urlPath := cli.BuildClientURL("v3", "keys", "query")
+ _, err = cli.MakeRequest("POST", urlPath, req, &resp)
+ return
+}
+
+func (cli *Client) ClaimKeys(req *ReqClaimKeys) (resp *RespClaimKeys, err error) {
+ urlPath := cli.BuildClientURL("v3", "keys", "claim")
+ _, err = cli.MakeRequest("POST", urlPath, req, &resp)
+ return
+}
+
+func (cli *Client) GetKeyChanges(from, to string) (resp *RespKeyChanges, err error) {
+ urlPath := cli.BuildURLWithQuery(ClientURLPath{"v3", "keys", "changes"}, map[string]string{
+ "from": from,
+ "to": to,
+ })
+ _, err = cli.MakeRequest("POST", urlPath, nil, &resp)
+ return
+}
+
+func (cli *Client) SendToDevice(eventType event.Type, req *ReqSendToDevice) (resp *RespSendToDevice, err error) {
+ urlPath := cli.BuildClientURL("v3", "sendToDevice", eventType.String(), cli.TxnID())
+ _, err = cli.MakeRequest("PUT", urlPath, req, &resp)
+ return
+}
+
+func (cli *Client) GetDevicesInfo() (resp *RespDevicesInfo, err error) {
+ urlPath := cli.BuildClientURL("v3", "devices")
+ _, err = cli.MakeRequest("GET", urlPath, nil, &resp)
+ return
+}
+
+func (cli *Client) GetDeviceInfo(deviceID id.DeviceID) (resp *RespDeviceInfo, err error) {
+ urlPath := cli.BuildClientURL("v3", "devices", deviceID)
+ _, err = cli.MakeRequest("GET", urlPath, nil, &resp)
+ return
+}
+
+func (cli *Client) SetDeviceInfo(deviceID id.DeviceID, req *ReqDeviceInfo) error {
+ urlPath := cli.BuildClientURL("v3", "devices", deviceID)
+ _, err := cli.MakeRequest("PUT", urlPath, req, nil)
+ return err
+}
+
+func (cli *Client) DeleteDevice(deviceID id.DeviceID, req *ReqDeleteDevice) error {
+ urlPath := cli.BuildClientURL("v3", "devices", deviceID)
+ _, err := cli.MakeRequest("DELETE", urlPath, req, nil)
+ return err
+}
+
+func (cli *Client) DeleteDevices(req *ReqDeleteDevices) error {
+ urlPath := cli.BuildClientURL("v3", "delete_devices")
+ _, err := cli.MakeRequest("DELETE", urlPath, req, nil)
+ return err
+}
+
+type UIACallback = func(*RespUserInteractive) interface{}
+
+// UploadCrossSigningKeys uploads the given cross-signing keys to the server.
+// Because the endpoint requires user-interactive authentication a callback must be provided that,
+// given the UI auth parameters, produces the required result (or nil to end the flow).
+func (cli *Client) UploadCrossSigningKeys(keys *UploadCrossSigningKeysReq, uiaCallback UIACallback) error {
+ content, err := cli.MakeFullRequest(FullRequest{
+ Method: http.MethodPost,
+ URL: cli.BuildClientURL("v3", "keys", "device_signing", "upload"),
+ RequestJSON: keys,
+ SensitiveContent: keys.Auth != nil,
+ })
+ if respErr, ok := err.(HTTPError); ok && respErr.IsStatus(http.StatusUnauthorized) {
+ // try again with UI auth
+ var uiAuthResp RespUserInteractive
+ if err := json.Unmarshal(content, &uiAuthResp); err != nil {
+ return fmt.Errorf("failed to decode UIA response: %w", err)
+ }
+ auth := uiaCallback(&uiAuthResp)
+ if auth != nil {
+ keys.Auth = auth
+ return cli.UploadCrossSigningKeys(keys, uiaCallback)
+ }
+ }
+ return err
+}
+
+func (cli *Client) UploadSignatures(req *ReqUploadSignatures) (resp *RespUploadSignatures, err error) {
+ urlPath := cli.BuildClientURL("v3", "keys", "signatures", "upload")
+ _, err = cli.MakeRequest("POST", urlPath, req, &resp)
+ return
+}
+
+// GetPushRules returns the push notification rules for the global scope.
+func (cli *Client) GetPushRules() (*pushrules.PushRuleset, error) {
+ return cli.GetScopedPushRules("global")
+}
+
+// GetScopedPushRules returns the push notification rules for the given scope.
+func (cli *Client) GetScopedPushRules(scope string) (resp *pushrules.PushRuleset, err error) {
+ u, _ := url.Parse(cli.BuildClientURL("v3", "pushrules", scope))
+ // client.BuildURL returns the URL without a trailing slash, but the pushrules endpoint requires the slash.
+ u.Path += "/"
+ _, err = cli.MakeRequest("GET", u.String(), nil, &resp)
+ return
+}
+
+func (cli *Client) GetPushRule(scope string, kind pushrules.PushRuleType, ruleID string) (resp *pushrules.PushRule, err error) {
+ urlPath := cli.BuildClientURL("v3", "pushrules", scope, kind, ruleID)
+ _, err = cli.MakeRequest("GET", urlPath, nil, &resp)
+ if resp != nil {
+ resp.Type = kind
+ }
+ return
+}
+
+func (cli *Client) DeletePushRule(scope string, kind pushrules.PushRuleType, ruleID string) error {
+ urlPath := cli.BuildClientURL("v3", "pushrules", scope, kind, ruleID)
+ _, err := cli.MakeRequest("DELETE", urlPath, nil, nil)
+ return err
+}
+
+func (cli *Client) PutPushRule(scope string, kind pushrules.PushRuleType, ruleID string, req *ReqPutPushRule) error {
+ query := make(map[string]string)
+ if len(req.After) > 0 {
+ query["after"] = req.After
+ }
+ if len(req.Before) > 0 {
+ query["before"] = req.Before
+ }
+ urlPath := cli.BuildURLWithQuery(ClientURLPath{"v3", "pushrules", scope, kind, ruleID}, query)
+ _, err := cli.MakeRequest("PUT", urlPath, req, nil)
+ return err
+}
+
+// BatchSend sends a batch of historical events into a room. This is only available for appservices.
+//
+// See https://github.com/matrix-org/matrix-doc/pull/2716 for more info.
+func (cli *Client) BatchSend(roomID id.RoomID, req *ReqBatchSend) (resp *RespBatchSend, err error) {
+ path := ClientURLPath{"unstable", "org.matrix.msc2716", "rooms", roomID, "batch_send"}
+ query := map[string]string{
+ "prev_event_id": req.PrevEventID.String(),
+ }
+ if req.BeeperNewMessages {
+ query["com.beeper.new_messages"] = "true"
+ }
+ if req.BeeperMarkReadBy != "" {
+ query["com.beeper.mark_read_by"] = req.BeeperMarkReadBy.String()
+ }
+ if len(req.BatchID) > 0 {
+ query["batch_id"] = req.BatchID.String()
+ }
+ _, err = cli.MakeRequest("POST", cli.BuildURLWithQuery(path, query), req, &resp)
+ return
+}
+
+func (cli *Client) AppservicePing(id, txnID string) (resp *RespAppservicePing, err error) {
+ _, err = cli.MakeFullRequest(FullRequest{
+ Method: http.MethodPost,
+ URL: cli.BuildClientURL("unstable", "fi.mau.msc2659", "appservice", id, "ping"),
+ RequestJSON: &ReqAppservicePing{TxnID: txnID},
+ ResponseJSON: &resp,
+ // This endpoint intentionally returns 50x, so don't retry
+ MaxAttempts: 1,
+ })
+ return
+}
+
+func (cli *Client) BeeperMergeRooms(req *ReqBeeperMergeRoom) (resp *RespBeeperMergeRoom, err error) {
+ urlPath := cli.BuildClientURL("unstable", "com.beeper.chatmerging", "merge")
+ _, err = cli.MakeRequest(http.MethodPost, urlPath, req, &resp)
+ return
+}
+
+func (cli *Client) BeeperSplitRoom(req *ReqBeeperSplitRoom) (resp *RespBeeperSplitRoom, err error) {
+ urlPath := cli.BuildClientURL("unstable", "com.beeper.chatmerging", "rooms", req.RoomID, "split")
+ _, err = cli.MakeRequest(http.MethodPost, urlPath, req, &resp)
+ return
+}
+
+func (cli *Client) BeeperDeleteRoom(roomID id.RoomID) (err error) {
+ urlPath := cli.BuildClientURL("unstable", "com.beeper.yeet", "rooms", roomID, "delete")
+ _, err = cli.MakeRequest(http.MethodPost, urlPath, nil, nil)
+ return
+}
+
+// TxnID returns the next transaction ID.
+func (cli *Client) TxnID() string {
+ txnID := atomic.AddInt32(&cli.txnID, 1)
+ return fmt.Sprintf("mautrix-go_%d_%d", time.Now().UnixNano(), txnID)
+}
+
+// NewClient creates a new Matrix Client ready for syncing
+func NewClient(homeserverURL string, userID id.UserID, accessToken string) (*Client, error) {
+ hsURL, err := ParseAndNormalizeBaseURL(homeserverURL)
+ if err != nil {
+ return nil, err
+ }
+ cli := &Client{
+ AccessToken: accessToken,
+ UserAgent: DefaultUserAgent,
+ HomeserverURL: hsURL,
+ UserID: userID,
+ Client: &http.Client{Timeout: 180 * time.Second},
+ Syncer: NewDefaultSyncer(),
+ Log: zerolog.Nop(),
+ // 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: NewMemorySyncStore(),
+ }
+ cli.Logger = maulogadapt.ZeroAsMau(&cli.Log)
+ return cli, nil
+}
diff --git a/vendor/maunium.net/go/mautrix/crypto/attachment/attachments.go b/vendor/maunium.net/go/mautrix/crypto/attachment/attachments.go
new file mode 100644
index 00000000..e516fded
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/crypto/attachment/attachments.go
@@ -0,0 +1,239 @@
+// Copyright (c) 2022 Tulir Asokan
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package attachment
+
+import (
+ "crypto/aes"
+ "crypto/cipher"
+ "crypto/sha256"
+ "encoding/base64"
+ "errors"
+ "hash"
+ "io"
+
+ "maunium.net/go/mautrix/crypto/utils"
+)
+
+var (
+ HashMismatch = errors.New("mismatching SHA-256 digest")
+ UnsupportedVersion = errors.New("unsupported Matrix file encryption version")
+ UnsupportedAlgorithm = errors.New("unsupported JWK encryption algorithm")
+ InvalidKey = errors.New("failed to decode key")
+ InvalidInitVector = errors.New("failed to decode initialization vector")
+ InvalidHash = errors.New("failed to decode SHA-256 hash")
+ ReaderClosed = errors.New("encrypting reader was already closed")
+)
+
+var (
+ keyBase64Length = base64.RawURLEncoding.EncodedLen(utils.AESCTRKeyLength)
+ ivBase64Length = base64.RawStdEncoding.EncodedLen(utils.AESCTRIVLength)
+ hashBase64Length = base64.RawStdEncoding.EncodedLen(utils.SHAHashLength)
+)
+
+type JSONWebKey struct {
+ Key string `json:"k"`
+ Algorithm string `json:"alg"`
+ Extractable bool `json:"ext"`
+ KeyType string `json:"kty"`
+ KeyOps []string `json:"key_ops"`
+}
+
+type EncryptedFileHashes struct {
+ SHA256 string `json:"sha256"`
+}
+
+type decodedKeys struct {
+ key [utils.AESCTRKeyLength]byte
+ iv [utils.AESCTRIVLength]byte
+
+ sha256 [utils.SHAHashLength]byte
+}
+
+type EncryptedFile struct {
+ Key JSONWebKey `json:"key"`
+ InitVector string `json:"iv"`
+ Hashes EncryptedFileHashes `json:"hashes"`
+ Version string `json:"v"`
+
+ decoded *decodedKeys
+}
+
+func NewEncryptedFile() *EncryptedFile {
+ key, iv := utils.GenAttachmentA256CTR()
+ return &EncryptedFile{
+ Key: JSONWebKey{
+ Key: base64.RawURLEncoding.EncodeToString(key[:]),
+ Algorithm: "A256CTR",
+ Extractable: true,
+ KeyType: "oct",
+ KeyOps: []string{"encrypt", "decrypt"},
+ },
+ InitVector: base64.RawStdEncoding.EncodeToString(iv[:]),
+ Version: "v2",
+
+ decoded: &decodedKeys{key: key, iv: iv},
+ }
+}
+
+func (ef *EncryptedFile) decodeKeys(includeHash bool) error {
+ if ef.decoded != nil {
+ return nil
+ } else if len(ef.Key.Key) != keyBase64Length {
+ return InvalidKey
+ } else if len(ef.InitVector) != ivBase64Length {
+ return InvalidInitVector
+ } else if includeHash && len(ef.Hashes.SHA256) != hashBase64Length {
+ return InvalidHash
+ }
+ ef.decoded = &decodedKeys{}
+ _, err := base64.RawURLEncoding.Decode(ef.decoded.key[:], []byte(ef.Key.Key))
+ if err != nil {
+ return InvalidKey
+ }
+ _, err = base64.RawStdEncoding.Decode(ef.decoded.iv[:], []byte(ef.InitVector))
+ if err != nil {
+ return InvalidInitVector
+ }
+ if includeHash {
+ _, err = base64.RawStdEncoding.Decode(ef.decoded.sha256[:], []byte(ef.Hashes.SHA256))
+ if err != nil {
+ return InvalidHash
+ }
+ }
+ return nil
+}
+
+// Encrypt encrypts the given data, updates the SHA256 hash in the EncryptedFile struct and returns the ciphertext.
+//
+// Deprecated: this makes a copy for the ciphertext, which means 2x memory usage. EncryptInPlace is recommended.
+func (ef *EncryptedFile) Encrypt(plaintext []byte) []byte {
+ ciphertext := make([]byte, len(plaintext))
+ copy(ciphertext, plaintext)
+ ef.EncryptInPlace(ciphertext)
+ return ciphertext
+}
+
+// EncryptInPlace encrypts the given data in-place (i.e. the provided data is overridden with the ciphertext)
+// and updates the SHA256 hash in the EncryptedFile struct.
+func (ef *EncryptedFile) EncryptInPlace(data []byte) {
+ ef.decodeKeys(false)
+ utils.XorA256CTR(data, ef.decoded.key, ef.decoded.iv)
+ checksum := sha256.Sum256(data)
+ ef.Hashes.SHA256 = base64.RawStdEncoding.EncodeToString(checksum[:])
+}
+
+type encryptingReader struct {
+ stream cipher.Stream
+ hash hash.Hash
+ source io.Reader
+ file *EncryptedFile
+ closed bool
+
+ isDecrypting bool
+}
+
+func (r *encryptingReader) Read(dst []byte) (n int, err error) {
+ if r.closed {
+ return 0, ReaderClosed
+ } else if r.isDecrypting && r.file.decoded == nil {
+ if err = r.file.PrepareForDecryption(); err != nil {
+ return
+ }
+ }
+ n, err = r.source.Read(dst)
+ r.stream.XORKeyStream(dst[:n], dst[:n])
+ r.hash.Write(dst[:n])
+ return
+}
+
+func (r *encryptingReader) Close() (err error) {
+ closer, ok := r.source.(io.ReadCloser)
+ if ok {
+ err = closer.Close()
+ }
+ if r.isDecrypting {
+ var downloadedChecksum [utils.SHAHashLength]byte
+ r.hash.Sum(downloadedChecksum[:])
+ if downloadedChecksum != r.file.decoded.sha256 {
+ return HashMismatch
+ }
+ } else {
+ r.file.Hashes.SHA256 = base64.RawStdEncoding.EncodeToString(r.hash.Sum(nil))
+ }
+ r.closed = true
+ return
+}
+
+// EncryptStream wraps the given io.Reader in order to encrypt the data.
+//
+// The Close() method of the returned io.ReadCloser must be called for the SHA256 hash
+// in the EncryptedFile struct to be updated. The metadata is not valid before the hash
+// is filled.
+func (ef *EncryptedFile) EncryptStream(reader io.Reader) io.ReadCloser {
+ ef.decodeKeys(false)
+ block, _ := aes.NewCipher(ef.decoded.key[:])
+ return &encryptingReader{
+ stream: cipher.NewCTR(block, ef.decoded.iv[:]),
+ hash: sha256.New(),
+ source: reader,
+ file: ef,
+ }
+}
+
+// Decrypt decrypts the given data and returns the plaintext.
+//
+// Deprecated: this makes a copy for the plaintext data, which means 2x memory usage. DecryptInPlace is recommended.
+func (ef *EncryptedFile) Decrypt(ciphertext []byte) ([]byte, error) {
+ plaintext := make([]byte, len(ciphertext))
+ copy(plaintext, ciphertext)
+ return plaintext, ef.DecryptInPlace(plaintext)
+}
+
+// PrepareForDecryption checks that the version and algorithm are supported and decodes the base64 keys
+//
+// DecryptStream will call this with the first Read() call if this hasn't been called manually.
+//
+// DecryptInPlace will always call this automatically, so calling this manually is not necessary when using that function.
+func (ef *EncryptedFile) PrepareForDecryption() error {
+ if ef.Version != "v2" {
+ return UnsupportedVersion
+ } else if ef.Key.Algorithm != "A256CTR" {
+ return UnsupportedAlgorithm
+ } else if err := ef.decodeKeys(true); err != nil {
+ return err
+ }
+ return nil
+}
+
+// DecryptInPlace decrypts the given data in-place (i.e. the provided data is overridden with the plaintext).
+func (ef *EncryptedFile) DecryptInPlace(data []byte) error {
+ if err := ef.PrepareForDecryption(); err != nil {
+ return err
+ } else if ef.decoded.sha256 != sha256.Sum256(data) {
+ return HashMismatch
+ } else {
+ utils.XorA256CTR(data, ef.decoded.key, ef.decoded.iv)
+ return nil
+ }
+}
+
+// DecryptStream wraps the given io.Reader in order to decrypt the data.
+//
+// The first Read call will check the algorithm and decode keys, so it might return an error before actually reading anything.
+// If you want to validate the file before opening the stream, call PrepareForDecryption manually and check for errors.
+//
+// The Close call will validate the hash and return an error if it doesn't match.
+// In this case, the written data should be considered compromised and should not be used further.
+func (ef *EncryptedFile) DecryptStream(reader io.Reader) io.ReadCloser {
+ block, _ := aes.NewCipher(ef.decoded.key[:])
+ return &encryptingReader{
+ stream: cipher.NewCTR(block, ef.decoded.iv[:]),
+ hash: sha256.New(),
+ source: reader,
+ file: ef,
+ }
+}
diff --git a/vendor/maunium.net/go/mautrix/crypto/utils/utils.go b/vendor/maunium.net/go/mautrix/crypto/utils/utils.go
new file mode 100644
index 00000000..e320bca1
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/crypto/utils/utils.go
@@ -0,0 +1,133 @@
+// Copyright (c) 2020 Nikos Filippakis
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package utils
+
+import (
+ "crypto/aes"
+ "crypto/cipher"
+ "crypto/hmac"
+ "crypto/sha256"
+ "crypto/sha512"
+ "encoding/base64"
+ "math/rand"
+ "strings"
+
+ "golang.org/x/crypto/hkdf"
+ "golang.org/x/crypto/pbkdf2"
+
+ "maunium.net/go/mautrix/util/base58"
+)
+
+const (
+ // AESCTRKeyLength is the length of the AES256-CTR key used.
+ AESCTRKeyLength = 32
+ // AESCTRIVLength is the length of the AES256-CTR IV used.
+ AESCTRIVLength = 16
+ // HMACKeyLength is the length of the HMAC key used.
+ HMACKeyLength = 32
+ // SHAHashLength is the length of the SHA hash used.
+ SHAHashLength = 32
+)
+
+// XorA256CTR encrypts the input with the keystream generated by the AES256-CTR algorithm with the given arguments.
+func XorA256CTR(source []byte, key [AESCTRKeyLength]byte, iv [AESCTRIVLength]byte) []byte {
+ block, _ := aes.NewCipher(key[:])
+ cipher.NewCTR(block, iv[:]).XORKeyStream(source, source)
+ return source
+}
+
+// GenAttachmentA256CTR generates a new random AES256-CTR key and IV suitable for encrypting attachments.
+func GenAttachmentA256CTR() (key [AESCTRKeyLength]byte, iv [AESCTRIVLength]byte) {
+ _, err := rand.Read(key[:])
+ if err != nil {
+ panic(err)
+ }
+
+ // The last 8 bytes of the IV act as the counter in AES-CTR, which means they're left empty here
+ _, err = rand.Read(iv[:8])
+ if err != nil {
+ panic(err)
+ }
+ return
+}
+
+// GenA256CTRIV generates a random IV for AES256-CTR with the last bit set to zero.
+func GenA256CTRIV() (iv [AESCTRIVLength]byte) {
+ _, err := rand.Read(iv[:])
+ if err != nil {
+ panic(err)
+ }
+ iv[8] &= 0x7F
+ return
+}
+
+// DeriveKeysSHA256 derives an AES and a HMAC key from the given recovery key.
+func DeriveKeysSHA256(key []byte, name string) ([AESCTRKeyLength]byte, [HMACKeyLength]byte) {
+ var zeroBytes [32]byte
+
+ derivedHkdf := hkdf.New(sha256.New, key[:], zeroBytes[:], []byte(name))
+
+ var aesKey [AESCTRKeyLength]byte
+ var hmacKey [HMACKeyLength]byte
+ derivedHkdf.Read(aesKey[:])
+ derivedHkdf.Read(hmacKey[:])
+
+ return aesKey, hmacKey
+}
+
+// PBKDF2SHA512 generates a key of the given bit-length using the given passphrase, salt and iteration count.
+func PBKDF2SHA512(password []byte, salt []byte, iters int, keyLenBits int) []byte {
+ return pbkdf2.Key(password, salt, iters, keyLenBits/8, sha512.New)
+}
+
+// DecodeBase58RecoveryKey recovers the secret storage from a recovery key.
+func DecodeBase58RecoveryKey(recoveryKey string) []byte {
+ noSpaces := strings.ReplaceAll(recoveryKey, " ", "")
+ decoded := base58.Decode(noSpaces)
+ if len(decoded) != AESCTRKeyLength+3 { // AESCTRKeyLength bytes key and 3 bytes prefix / parity
+ return nil
+ }
+ var parity byte
+ for _, b := range decoded[:34] {
+ parity ^= b
+ }
+ if parity != decoded[34] || decoded[0] != 0x8B || decoded[1] != 1 {
+ return nil
+ }
+ return decoded[2:34]
+}
+
+// EncodeBase58RecoveryKey recovers the secret storage from a recovery key.
+func EncodeBase58RecoveryKey(key []byte) string {
+ var inputBytes [35]byte
+ copy(inputBytes[2:34], key[:])
+ inputBytes[0] = 0x8B
+ inputBytes[1] = 1
+
+ var parity byte
+ for _, b := range inputBytes[:34] {
+ parity ^= b
+ }
+ inputBytes[34] = parity
+ recoveryKey := base58.Encode(inputBytes[:])
+
+ var spacedKey string
+ for i, c := range recoveryKey {
+ if i > 0 && i%4 == 0 {
+ spacedKey += " "
+ }
+ spacedKey += string(c)
+ }
+ return spacedKey
+}
+
+// HMACSHA256B64 calculates the base64 of the SHA256 hmac of the input with the given key.
+func HMACSHA256B64(input []byte, hmacKey [HMACKeyLength]byte) string {
+ h := hmac.New(sha256.New, hmacKey[:])
+ h.Write(input)
+ return base64.StdEncoding.EncodeToString(h.Sum(nil))
+}
diff --git a/vendor/maunium.net/go/mautrix/error.go b/vendor/maunium.net/go/mautrix/error.go
new file mode 100644
index 00000000..9c5e8a0e
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/error.go
@@ -0,0 +1,154 @@
+// Copyright (c) 2020 Tulir Asokan
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package mautrix
+
+import (
+ "encoding/json"
+ "errors"
+ "fmt"
+ "net/http"
+)
+
+// Common error codes from https://matrix.org/docs/spec/client_server/latest#api-standards
+//
+// Can be used with errors.Is() to check the response code without casting the error:
+//
+// err := client.Sync()
+// if errors.Is(err, MUnknownToken) {
+// // logout
+// }
+var (
+ // Forbidden access, e.g. joining a room without permission, failed login.
+ MForbidden = RespError{ErrCode: "M_FORBIDDEN"}
+ // Unrecognized request, e.g. the endpoint does not exist or is not implemented.
+ MUnrecognized = RespError{ErrCode: "M_UNRECOGNIZED"}
+ // The access token specified was not recognised.
+ MUnknownToken = RespError{ErrCode: "M_UNKNOWN_TOKEN"}
+ // No access token was specified for the request.
+ MMissingToken = RespError{ErrCode: "M_MISSING_TOKEN"}
+ // Request contained valid JSON, but it was malformed in some way, e.g. missing required keys, invalid values for keys.
+ MBadJSON = RespError{ErrCode: "M_BAD_JSON"}
+ // Request did not contain valid JSON.
+ MNotJSON = RespError{ErrCode: "M_NOT_JSON"}
+ // No resource was found for this request.
+ MNotFound = RespError{ErrCode: "M_NOT_FOUND"}
+ // Too many requests have been sent in a short period of time. Wait a while then try again.
+ MLimitExceeded = RespError{ErrCode: "M_LIMIT_EXCEEDED"}
+ // The user ID associated with the request has been deactivated.
+ // Typically for endpoints that prove authentication, such as /login.
+ MUserDeactivated = RespError{ErrCode: "M_USER_DEACTIVATED"}
+ // Encountered when trying to register a user ID which has been taken.
+ MUserInUse = RespError{ErrCode: "M_USER_IN_USE"}
+ // Encountered when trying to register a user ID which is not valid.
+ MInvalidUsername = RespError{ErrCode: "M_INVALID_USERNAME"}
+ // Sent when the room alias given to the createRoom API is already in use.
+ MRoomInUse = RespError{ErrCode: "M_ROOM_IN_USE"}
+ // The state change requested cannot be performed, such as attempting to unban a user who is not banned.
+ MBadState = RespError{ErrCode: "M_BAD_STATE"}
+ // The request or entity was too large.
+ MTooLarge = RespError{ErrCode: "M_TOO_LARGE"}
+ // The resource being requested is reserved by an application service, or the application service making the request has not created the resource.
+ MExclusive = RespError{ErrCode: "M_EXCLUSIVE"}
+ // The client's request to create a room used a room version that the server does not support.
+ MUnsupportedRoomVersion = RespError{ErrCode: "M_UNSUPPORTED_ROOM_VERSION"}
+ // The client attempted to join a room that has a version the server does not support.
+ // Inspect the room_version property of the error response for the room's version.
+ MIncompatibleRoomVersion = RespError{ErrCode: "M_INCOMPATIBLE_ROOM_VERSION"}
+ // The client specified a parameter that has the wrong value.
+ MInvalidParam = RespError{ErrCode: "M_INVALID_PARAM"}
+
+ MSC2659URLNotSet = RespError{ErrCode: "FI.MAU.MSC2659_URL_NOT_SET"}
+ MSC2659BadStatus = RespError{ErrCode: "FI.MAU.MSC2659_BAD_STATUS"}
+ MSC2659ConnectionTimeout = RespError{ErrCode: "FI.MAU.MSC2659_CONNECTION_TIMEOUT"}
+ MSC2659ConnectionFailed = RespError{ErrCode: "FI.MAU.MSC2659_CONNECTION_FAILED"}
+)
+
+// HTTPError An HTTP Error response, which may wrap an underlying native Go Error.
+type HTTPError struct {
+ Request *http.Request
+ Response *http.Response
+ ResponseBody string
+
+ WrappedError error
+ RespError *RespError
+ Message string
+}
+
+func (e HTTPError) Is(err error) bool {
+ return (e.RespError != nil && errors.Is(e.RespError, err)) || (e.WrappedError != nil && errors.Is(e.WrappedError, err))
+}
+
+func (e HTTPError) IsStatus(code int) bool {
+ return e.Response != nil && e.Response.StatusCode == code
+}
+
+func (e HTTPError) Error() string {
+ if e.WrappedError != nil {
+ return fmt.Sprintf("%s: %v", e.Message, e.WrappedError)
+ } else if e.RespError != nil {
+ return fmt.Sprintf("failed to %s %s: %s (HTTP %d): %s", e.Request.Method, e.Request.URL.Path,
+ e.RespError.ErrCode, e.Response.StatusCode, e.RespError.Err)
+ } else {
+ msg := fmt.Sprintf("failed to %s %s: %s", e.Request.Method, e.Request.URL.Path, e.Response.Status)
+ if len(e.ResponseBody) > 0 {
+ msg = fmt.Sprintf("%s\n%s", msg, e.ResponseBody)
+ }
+ return msg
+ }
+}
+
+func (e HTTPError) Unwrap() error {
+ if e.WrappedError != nil {
+ return e.WrappedError
+ } else if e.RespError != nil {
+ return *e.RespError
+ }
+ return nil
+}
+
+// RespError is the standard JSON error response from Homeservers. It also implements the Golang "error" interface.
+// See https://spec.matrix.org/v1.2/client-server-api/#api-standards
+type RespError struct {
+ ErrCode string
+ Err string
+ ExtraData map[string]interface{}
+}
+
+func (e *RespError) UnmarshalJSON(data []byte) error {
+ err := json.Unmarshal(data, &e.ExtraData)
+ if err != nil {
+ return err
+ }
+ e.ErrCode, _ = e.ExtraData["errcode"].(string)
+ e.Err, _ = e.ExtraData["error"].(string)
+ return nil
+}
+
+func (e *RespError) MarshalJSON() ([]byte, error) {
+ if e.ExtraData == nil {
+ e.ExtraData = make(map[string]interface{})
+ }
+ e.ExtraData["errcode"] = e.ErrCode
+ e.ExtraData["error"] = e.Err
+ return json.Marshal(&e.ExtraData)
+}
+
+// Error returns the errcode and error message.
+func (e RespError) Error() string {
+ return e.ErrCode + ": " + e.Err
+}
+
+func (e RespError) Is(err error) bool {
+ e2, ok := err.(RespError)
+ if !ok {
+ return false
+ }
+ if e.ErrCode == "M_UNKNOWN" && e2.ErrCode == "M_UNKNOWN" {
+ return e.Err == e2.Err
+ }
+ return e2.ErrCode == e.ErrCode
+}
diff --git a/vendor/maunium.net/go/mautrix/event/accountdata.go b/vendor/maunium.net/go/mautrix/event/accountdata.go
new file mode 100644
index 00000000..6637fcfe
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/event/accountdata.go
@@ -0,0 +1,45 @@
+// Copyright (c) 2020 Tulir Asokan
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package event
+
+import (
+ "encoding/json"
+
+ "maunium.net/go/mautrix/id"
+)
+
+// TagEventContent represents the content of a m.tag room account data event.
+// https://spec.matrix.org/v1.2/client-server-api/#mtag
+type TagEventContent struct {
+ Tags Tags `json:"tags"`
+}
+
+type Tags map[string]Tag
+
+type Tag struct {
+ Order json.Number `json:"order,omitempty"`
+}
+
+// DirectChatsEventContent represents the content of a m.direct account data event.
+// https://spec.matrix.org/v1.2/client-server-api/#mdirect
+type DirectChatsEventContent map[id.UserID][]id.RoomID
+
+// FullyReadEventContent represents the content of a m.fully_read account data event.
+// https://spec.matrix.org/v1.2/client-server-api/#mfully_read
+type FullyReadEventContent struct {
+ EventID id.EventID `json:"event_id"`
+}
+
+// IgnoredUserListEventContent represents the content of a m.ignored_user_list account data event.
+// https://spec.matrix.org/v1.2/client-server-api/#mignored_user_list
+type IgnoredUserListEventContent struct {
+ IgnoredUsers map[id.UserID]IgnoredUser `json:"ignored_users"`
+}
+
+type IgnoredUser struct {
+ // This is an empty object
+}
diff --git a/vendor/maunium.net/go/mautrix/event/beeper.go b/vendor/maunium.net/go/mautrix/event/beeper.go
new file mode 100644
index 00000000..2ee72073
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/event/beeper.go
@@ -0,0 +1,51 @@
+// Copyright (c) 2022 Tulir Asokan
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package event
+
+import (
+ "maunium.net/go/mautrix/id"
+)
+
+type MessageStatusReason string
+
+const (
+ MessageStatusGenericError MessageStatusReason = "m.event_not_handled"
+ MessageStatusUnsupported MessageStatusReason = "com.beeper.unsupported_event"
+ MessageStatusUndecryptable MessageStatusReason = "com.beeper.undecryptable_event"
+ MessageStatusTooOld MessageStatusReason = "m.event_too_old"
+ MessageStatusNetworkError MessageStatusReason = "m.foreign_network_error"
+ MessageStatusNoPermission MessageStatusReason = "m.no_permission"
+ MessageStatusBridgeUnavailable MessageStatusReason = "m.bridge_unavailable"
+)
+
+type MessageStatus string
+
+const (
+ MessageStatusSuccess MessageStatus = "SUCCESS"
+ MessageStatusPending MessageStatus = "PENDING"
+ MessageStatusRetriable MessageStatus = "FAIL_RETRIABLE"
+ MessageStatusFail MessageStatus = "FAIL_PERMANENT"
+)
+
+type BeeperMessageStatusEventContent struct {
+ Network string `json:"network"`
+ RelatesTo RelatesTo `json:"m.relates_to"`
+ Status MessageStatus `json:"status"`
+ Reason MessageStatusReason `json:"reason,omitempty"`
+ Error string `json:"error,omitempty"`
+ Message string `json:"message,omitempty"`
+
+ LastRetry id.EventID `json:"last_retry,omitempty"`
+
+ MutateEventKey string `json:"mutate_event_key,omitempty"`
+}
+
+type BeeperRetryMetadata struct {
+ OriginalEventID id.EventID `json:"original_event_id"`
+ RetryCount int `json:"retry_count"`
+ // last_retry is also present, but not used by bridges
+}
diff --git a/vendor/maunium.net/go/mautrix/event/content.go b/vendor/maunium.net/go/mautrix/event/content.go
new file mode 100644
index 00000000..5624fd59
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/event/content.go
@@ -0,0 +1,506 @@
+// Copyright (c) 2021 Tulir Asokan
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package event
+
+import (
+ "encoding/gob"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "reflect"
+)
+
+// TypeMap is a mapping from event type to the content struct type.
+// This is used by Content.ParseRaw() for creating the correct type of struct.
+var TypeMap = map[Type]reflect.Type{
+ StateMember: reflect.TypeOf(MemberEventContent{}),
+ StatePowerLevels: reflect.TypeOf(PowerLevelsEventContent{}),
+ StateCanonicalAlias: reflect.TypeOf(CanonicalAliasEventContent{}),
+ StateRoomName: reflect.TypeOf(RoomNameEventContent{}),
+ StateRoomAvatar: reflect.TypeOf(RoomAvatarEventContent{}),
+ StateServerACL: reflect.TypeOf(ServerACLEventContent{}),
+ StateTopic: reflect.TypeOf(TopicEventContent{}),
+ StateTombstone: reflect.TypeOf(TombstoneEventContent{}),
+ StateCreate: reflect.TypeOf(CreateEventContent{}),
+ StateJoinRules: reflect.TypeOf(JoinRulesEventContent{}),
+ StateHistoryVisibility: reflect.TypeOf(HistoryVisibilityEventContent{}),
+ StateGuestAccess: reflect.TypeOf(GuestAccessEventContent{}),
+ StatePinnedEvents: reflect.TypeOf(PinnedEventsEventContent{}),
+ StatePolicyRoom: reflect.TypeOf(ModPolicyContent{}),
+ StatePolicyServer: reflect.TypeOf(ModPolicyContent{}),
+ StatePolicyUser: reflect.TypeOf(ModPolicyContent{}),
+ StateEncryption: reflect.TypeOf(EncryptionEventContent{}),
+ StateBridge: reflect.TypeOf(BridgeEventContent{}),
+ StateHalfShotBridge: reflect.TypeOf(BridgeEventContent{}),
+ StateSpaceParent: reflect.TypeOf(SpaceParentEventContent{}),
+ StateSpaceChild: reflect.TypeOf(SpaceChildEventContent{}),
+ StateInsertionMarker: reflect.TypeOf(InsertionMarkerContent{}),
+
+ EventMessage: reflect.TypeOf(MessageEventContent{}),
+ EventSticker: reflect.TypeOf(MessageEventContent{}),
+ EventEncrypted: reflect.TypeOf(EncryptedEventContent{}),
+ EventRedaction: reflect.TypeOf(RedactionEventContent{}),
+ EventReaction: reflect.TypeOf(ReactionEventContent{}),
+
+ BeeperMessageStatus: reflect.TypeOf(BeeperMessageStatusEventContent{}),
+
+ AccountDataRoomTags: reflect.TypeOf(TagEventContent{}),
+ AccountDataDirectChats: reflect.TypeOf(DirectChatsEventContent{}),
+ AccountDataFullyRead: reflect.TypeOf(FullyReadEventContent{}),
+ AccountDataIgnoredUserList: reflect.TypeOf(IgnoredUserListEventContent{}),
+
+ EphemeralEventTyping: reflect.TypeOf(TypingEventContent{}),
+ EphemeralEventReceipt: reflect.TypeOf(ReceiptEventContent{}),
+ EphemeralEventPresence: reflect.TypeOf(PresenceEventContent{}),
+
+ InRoomVerificationStart: reflect.TypeOf(VerificationStartEventContent{}),
+ InRoomVerificationReady: reflect.TypeOf(VerificationReadyEventContent{}),
+ InRoomVerificationAccept: reflect.TypeOf(VerificationAcceptEventContent{}),
+ InRoomVerificationKey: reflect.TypeOf(VerificationKeyEventContent{}),
+ InRoomVerificationMAC: reflect.TypeOf(VerificationMacEventContent{}),
+ InRoomVerificationCancel: reflect.TypeOf(VerificationCancelEventContent{}),
+
+ ToDeviceRoomKey: reflect.TypeOf(RoomKeyEventContent{}),
+ ToDeviceForwardedRoomKey: reflect.TypeOf(ForwardedRoomKeyEventContent{}),
+ ToDeviceRoomKeyRequest: reflect.TypeOf(RoomKeyRequestEventContent{}),
+ ToDeviceEncrypted: reflect.TypeOf(EncryptedEventContent{}),
+ ToDeviceRoomKeyWithheld: reflect.TypeOf(RoomKeyWithheldEventContent{}),
+ ToDeviceDummy: reflect.TypeOf(DummyEventContent{}),
+
+ ToDeviceVerificationStart: reflect.TypeOf(VerificationStartEventContent{}),
+ ToDeviceVerificationAccept: reflect.TypeOf(VerificationAcceptEventContent{}),
+ ToDeviceVerificationKey: reflect.TypeOf(VerificationKeyEventContent{}),
+ ToDeviceVerificationMAC: reflect.TypeOf(VerificationMacEventContent{}),
+ ToDeviceVerificationCancel: reflect.TypeOf(VerificationCancelEventContent{}),
+ ToDeviceVerificationRequest: reflect.TypeOf(VerificationRequestEventContent{}),
+
+ ToDeviceOrgMatrixRoomKeyWithheld: reflect.TypeOf(RoomKeyWithheldEventContent{}),
+
+ CallInvite: reflect.TypeOf(CallInviteEventContent{}),
+ CallCandidates: reflect.TypeOf(CallCandidatesEventContent{}),
+ CallAnswer: reflect.TypeOf(CallAnswerEventContent{}),
+ CallReject: reflect.TypeOf(CallRejectEventContent{}),
+ CallSelectAnswer: reflect.TypeOf(CallSelectAnswerEventContent{}),
+ CallNegotiate: reflect.TypeOf(CallNegotiateEventContent{}),
+ CallHangup: reflect.TypeOf(CallHangupEventContent{}),
+}
+
+// Content stores the content of a Matrix event.
+//
+// By default, the raw JSON bytes are stored in VeryRaw and parsed into a map[string]interface{} in the Raw field.
+// Additionally, you can call ParseRaw with the correct event type to parse the (VeryRaw) content into a nicer struct,
+// which you can then access from Parsed or via the helper functions.
+//
+// When being marshaled into JSON, the data in Parsed will be marshaled first and then recursively merged
+// with the data in Raw. Values in Raw are preferred, but nested objects will be recursed into before merging,
+// rather than overriding the whole object with the one in Raw).
+// If one of them is nil, the only the other is used. If both (Parsed and Raw) are nil, VeryRaw is used instead.
+type Content struct {
+ VeryRaw json.RawMessage
+ Raw map[string]interface{}
+ Parsed interface{}
+}
+
+type Relatable interface {
+ GetRelatesTo() *RelatesTo
+ OptionalGetRelatesTo() *RelatesTo
+ SetRelatesTo(rel *RelatesTo)
+}
+
+func (content *Content) UnmarshalJSON(data []byte) error {
+ content.VeryRaw = data
+ err := json.Unmarshal(data, &content.Raw)
+ return err
+}
+
+func (content *Content) MarshalJSON() ([]byte, error) {
+ if content.Raw == nil {
+ if content.Parsed == nil {
+ if content.VeryRaw == nil {
+ return []byte("{}"), nil
+ }
+ return content.VeryRaw, nil
+ }
+ return json.Marshal(content.Parsed)
+ } else if content.Parsed != nil {
+ // TODO this whole thing is incredibly hacky
+ // It needs to produce JSON, where:
+ // * content.Parsed is applied after content.Raw
+ // * MarshalJSON() is respected inside content.Parsed
+ // * Custom field inside nested objects of content.Raw are preserved,
+ // even if content.Parsed contains the higher-level objects.
+ // * content.Raw is not modified
+
+ unparsed, err := json.Marshal(content.Parsed)
+ if err != nil {
+ return nil, err
+ }
+
+ var rawParsed map[string]interface{}
+ err = json.Unmarshal(unparsed, &rawParsed)
+ if err != nil {
+ return nil, err
+ }
+
+ output := make(map[string]interface{})
+ for key, value := range content.Raw {
+ output[key] = value
+ }
+
+ mergeMaps(output, rawParsed)
+ return json.Marshal(output)
+ }
+ return json.Marshal(content.Raw)
+}
+
+// Deprecated: use errors.Is directly
+func IsUnsupportedContentType(err error) bool {
+ return errors.Is(err, ErrUnsupportedContentType)
+}
+
+var ErrContentAlreadyParsed = errors.New("content is already parsed")
+var ErrUnsupportedContentType = errors.New("unsupported event type")
+
+func (content *Content) ParseRaw(evtType Type) error {
+ if content.Parsed != nil {
+ return ErrContentAlreadyParsed
+ }
+ structType, ok := TypeMap[evtType]
+ if !ok {
+ return fmt.Errorf("%w %s", ErrUnsupportedContentType, evtType.Repr())
+ }
+ content.Parsed = reflect.New(structType).Interface()
+ return json.Unmarshal(content.VeryRaw, &content.Parsed)
+}
+
+func mergeMaps(into, from map[string]interface{}) {
+ for key, newValue := range from {
+ existingValue, ok := into[key]
+ if !ok {
+ into[key] = newValue
+ continue
+ }
+ existingValueMap, okEx := existingValue.(map[string]interface{})
+ newValueMap, okNew := newValue.(map[string]interface{})
+ if okEx && okNew {
+ mergeMaps(existingValueMap, newValueMap)
+ } else {
+ into[key] = newValue
+ }
+ }
+}
+
+func init() {
+ gob.Register(&MemberEventContent{})
+ gob.Register(&PowerLevelsEventContent{})
+ gob.Register(&CanonicalAliasEventContent{})
+ gob.Register(&EncryptionEventContent{})
+ gob.Register(&BridgeEventContent{})
+ gob.Register(&SpaceChildEventContent{})
+ gob.Register(&SpaceParentEventContent{})
+ gob.Register(&RoomNameEventContent{})
+ gob.Register(&RoomAvatarEventContent{})
+ gob.Register(&TopicEventContent{})
+ gob.Register(&TombstoneEventContent{})
+ gob.Register(&CreateEventContent{})
+ gob.Register(&JoinRulesEventContent{})
+ gob.Register(&HistoryVisibilityEventContent{})
+ gob.Register(&GuestAccessEventContent{})
+ gob.Register(&PinnedEventsEventContent{})
+ gob.Register(&MessageEventContent{})
+ gob.Register(&MessageEventContent{})
+ gob.Register(&EncryptedEventContent{})
+ gob.Register(&RedactionEventContent{})
+ gob.Register(&ReactionEventContent{})
+ gob.Register(&TagEventContent{})
+ gob.Register(&DirectChatsEventContent{})
+ gob.Register(&FullyReadEventContent{})
+ gob.Register(&IgnoredUserListEventContent{})
+ gob.Register(&TypingEventContent{})
+ gob.Register(&ReceiptEventContent{})
+ gob.Register(&PresenceEventContent{})
+ gob.Register(&RoomKeyEventContent{})
+ gob.Register(&ForwardedRoomKeyEventContent{})
+ gob.Register(&RoomKeyRequestEventContent{})
+ gob.Register(&RoomKeyWithheldEventContent{})
+}
+
+// Helper cast functions below
+
+func (content *Content) AsMember() *MemberEventContent {
+ casted, ok := content.Parsed.(*MemberEventContent)
+ if !ok {
+ return &MemberEventContent{}
+ }
+ return casted
+}
+func (content *Content) AsPowerLevels() *PowerLevelsEventContent {
+ casted, ok := content.Parsed.(*PowerLevelsEventContent)
+ if !ok {
+ return &PowerLevelsEventContent{}
+ }
+ return casted
+}
+func (content *Content) AsCanonicalAlias() *CanonicalAliasEventContent {
+ casted, ok := content.Parsed.(*CanonicalAliasEventContent)
+ if !ok {
+ return &CanonicalAliasEventContent{}
+ }
+ return casted
+}
+func (content *Content) AsRoomName() *RoomNameEventContent {
+ casted, ok := content.Parsed.(*RoomNameEventContent)
+ if !ok {
+ return &RoomNameEventContent{}
+ }
+ return casted
+}
+func (content *Content) AsRoomAvatar() *RoomAvatarEventContent {
+ casted, ok := content.Parsed.(*RoomAvatarEventContent)
+ if !ok {
+ return &RoomAvatarEventContent{}
+ }
+ return casted
+}
+func (content *Content) AsTopic() *TopicEventContent {
+ casted, ok := content.Parsed.(*TopicEventContent)
+ if !ok {
+ return &TopicEventContent{}
+ }
+ return casted
+}
+func (content *Content) AsTombstone() *TombstoneEventContent {
+ casted, ok := content.Parsed.(*TombstoneEventContent)
+ if !ok {
+ return &TombstoneEventContent{}
+ }
+ return casted
+}
+func (content *Content) AsCreate() *CreateEventContent {
+ casted, ok := content.Parsed.(*CreateEventContent)
+ if !ok {
+ return &CreateEventContent{}
+ }
+ return casted
+}
+func (content *Content) AsJoinRules() *JoinRulesEventContent {
+ casted, ok := content.Parsed.(*JoinRulesEventContent)
+ if !ok {
+ return &JoinRulesEventContent{}
+ }
+ return casted
+}
+func (content *Content) AsHistoryVisibility() *HistoryVisibilityEventContent {
+ casted, ok := content.Parsed.(*HistoryVisibilityEventContent)
+ if !ok {
+ return &HistoryVisibilityEventContent{}
+ }
+ return casted
+}
+func (content *Content) AsGuestAccess() *GuestAccessEventContent {
+ casted, ok := content.Parsed.(*GuestAccessEventContent)
+ if !ok {
+ return &GuestAccessEventContent{}
+ }
+ return casted
+}
+func (content *Content) AsPinnedEvents() *PinnedEventsEventContent {
+ casted, ok := content.Parsed.(*PinnedEventsEventContent)
+ if !ok {
+ return &PinnedEventsEventContent{}
+ }
+ return casted
+}
+func (content *Content) AsEncryption() *EncryptionEventContent {
+ casted, ok := content.Parsed.(*EncryptionEventContent)
+ if !ok {
+ return &EncryptionEventContent{}
+ }
+ return casted
+}
+func (content *Content) AsBridge() *BridgeEventContent {
+ casted, ok := content.Parsed.(*BridgeEventContent)
+ if !ok {
+ return &BridgeEventContent{}
+ }
+ return casted
+}
+func (content *Content) AsSpaceChild() *SpaceChildEventContent {
+ casted, ok := content.Parsed.(*SpaceChildEventContent)
+ if !ok {
+ return &SpaceChildEventContent{}
+ }
+ return casted
+}
+func (content *Content) AsSpaceParent() *SpaceParentEventContent {
+ casted, ok := content.Parsed.(*SpaceParentEventContent)
+ if !ok {
+ return &SpaceParentEventContent{}
+ }
+ return casted
+}
+func (content *Content) AsMessage() *MessageEventContent {
+ casted, ok := content.Parsed.(*MessageEventContent)
+ if !ok {
+ return &MessageEventContent{}
+ }
+ return casted
+}
+func (content *Content) AsEncrypted() *EncryptedEventContent {
+ casted, ok := content.Parsed.(*EncryptedEventContent)
+ if !ok {
+ return &EncryptedEventContent{}
+ }
+ return casted
+}
+func (content *Content) AsRedaction() *RedactionEventContent {
+ casted, ok := content.Parsed.(*RedactionEventContent)
+ if !ok {
+ return &RedactionEventContent{}
+ }
+ return casted
+}
+func (content *Content) AsReaction() *ReactionEventContent {
+ casted, ok := content.Parsed.(*ReactionEventContent)
+ if !ok {
+ return &ReactionEventContent{}
+ }
+ return casted
+}
+func (content *Content) AsTag() *TagEventContent {
+ casted, ok := content.Parsed.(*TagEventContent)
+ if !ok {
+ return &TagEventContent{}
+ }
+ return casted
+}
+func (content *Content) AsDirectChats() *DirectChatsEventContent {
+ casted, ok := content.Parsed.(*DirectChatsEventContent)
+ if !ok {
+ return &DirectChatsEventContent{}
+ }
+ return casted
+}
+func (content *Content) AsFullyRead() *FullyReadEventContent {
+ casted, ok := content.Parsed.(*FullyReadEventContent)
+ if !ok {
+ return &FullyReadEventContent{}
+ }
+ return casted
+}
+func (content *Content) AsIgnoredUserList() *IgnoredUserListEventContent {
+ casted, ok := content.Parsed.(*IgnoredUserListEventContent)
+ if !ok {
+ return &IgnoredUserListEventContent{}
+ }
+ return casted
+}
+func (content *Content) AsTyping() *TypingEventContent {
+ casted, ok := content.Parsed.(*TypingEventContent)
+ if !ok {
+ return &TypingEventContent{}
+ }
+ return casted
+}
+func (content *Content) AsReceipt() *ReceiptEventContent {
+ casted, ok := content.Parsed.(*ReceiptEventContent)
+ if !ok {
+ return &ReceiptEventContent{}
+ }
+ return casted
+}
+func (content *Content) AsPresence() *PresenceEventContent {
+ casted, ok := content.Parsed.(*PresenceEventContent)
+ if !ok {
+ return &PresenceEventContent{}
+ }
+ return casted
+}
+func (content *Content) AsRoomKey() *RoomKeyEventContent {
+ casted, ok := content.Parsed.(*RoomKeyEventContent)
+ if !ok {
+ return &RoomKeyEventContent{}
+ }
+ return casted
+}
+func (content *Content) AsForwardedRoomKey() *ForwardedRoomKeyEventContent {
+ casted, ok := content.Parsed.(*ForwardedRoomKeyEventContent)
+ if !ok {
+ return &ForwardedRoomKeyEventContent{}
+ }
+ return casted
+}
+func (content *Content) AsRoomKeyRequest() *RoomKeyRequestEventContent {
+ casted, ok := content.Parsed.(*RoomKeyRequestEventContent)
+ if !ok {
+ return &RoomKeyRequestEventContent{}
+ }
+ return casted
+}
+func (content *Content) AsRoomKeyWithheld() *RoomKeyWithheldEventContent {
+ casted, ok := content.Parsed.(*RoomKeyWithheldEventContent)
+ if !ok {
+ return &RoomKeyWithheldEventContent{}
+ }
+ return casted
+}
+func (content *Content) AsCallInvite() *CallInviteEventContent {
+ casted, ok := content.Parsed.(*CallInviteEventContent)
+ if !ok {
+ return &CallInviteEventContent{}
+ }
+ return casted
+}
+func (content *Content) AsCallCandidates() *CallCandidatesEventContent {
+ casted, ok := content.Parsed.(*CallCandidatesEventContent)
+ if !ok {
+ return &CallCandidatesEventContent{}
+ }
+ return casted
+}
+func (content *Content) AsCallAnswer() *CallAnswerEventContent {
+ casted, ok := content.Parsed.(*CallAnswerEventContent)
+ if !ok {
+ return &CallAnswerEventContent{}
+ }
+ return casted
+}
+func (content *Content) AsCallReject() *CallRejectEventContent {
+ casted, ok := content.Parsed.(*CallRejectEventContent)
+ if !ok {
+ return &CallRejectEventContent{}
+ }
+ return casted
+}
+func (content *Content) AsCallSelectAnswer() *CallSelectAnswerEventContent {
+ casted, ok := content.Parsed.(*CallSelectAnswerEventContent)
+ if !ok {
+ return &CallSelectAnswerEventContent{}
+ }
+ return casted
+}
+func (content *Content) AsCallNegotiate() *CallNegotiateEventContent {
+ casted, ok := content.Parsed.(*CallNegotiateEventContent)
+ if !ok {
+ return &CallNegotiateEventContent{}
+ }
+ return casted
+}
+func (content *Content) AsCallHangup() *CallHangupEventContent {
+ casted, ok := content.Parsed.(*CallHangupEventContent)
+ if !ok {
+ return &CallHangupEventContent{}
+ }
+ return casted
+}
+func (content *Content) AsModPolicy() *ModPolicyContent {
+ casted, ok := content.Parsed.(*ModPolicyContent)
+ if !ok {
+ return &ModPolicyContent{}
+ }
+ return casted
+}
diff --git a/vendor/maunium.net/go/mautrix/event/encryption.go b/vendor/maunium.net/go/mautrix/event/encryption.go
new file mode 100644
index 00000000..2506ad57
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/event/encryption.go
@@ -0,0 +1,149 @@
+// Copyright (c) 2020 Tulir Asokan
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package event
+
+import (
+ "encoding/json"
+
+ "maunium.net/go/mautrix/id"
+)
+
+// EncryptionEventContent represents the content of a m.room.encryption state event.
+// https://spec.matrix.org/v1.2/client-server-api/#mroomencryption
+type EncryptionEventContent struct {
+ // The encryption algorithm to be used to encrypt messages sent in this room. Must be 'm.megolm.v1.aes-sha2'.
+ Algorithm id.Algorithm `json:"algorithm"`
+ // How long the session should be used before changing it. 604800000 (a week) is the recommended default.
+ RotationPeriodMillis int64 `json:"rotation_period_ms,omitempty"`
+ // How many messages should be sent before changing the session. 100 is the recommended default.
+ RotationPeriodMessages int `json:"rotation_period_msgs,omitempty"`
+}
+
+// EncryptedEventContent represents the content of a m.room.encrypted message event.
+// https://spec.matrix.org/v1.2/client-server-api/#mroomencrypted
+//
+// Note that sender_key and device_id are deprecated in Megolm events as of https://github.com/matrix-org/matrix-spec-proposals/pull/3700
+type EncryptedEventContent struct {
+ Algorithm id.Algorithm `json:"algorithm"`
+ SenderKey id.SenderKey `json:"sender_key,omitempty"`
+ // Deprecated: Matrix v1.3
+ DeviceID id.DeviceID `json:"device_id,omitempty"`
+ // Only present for Megolm events
+ SessionID id.SessionID `json:"session_id,omitempty"`
+
+ Ciphertext json.RawMessage `json:"ciphertext"`
+
+ MegolmCiphertext []byte `json:"-"`
+ OlmCiphertext OlmCiphertexts `json:"-"`
+
+ RelatesTo *RelatesTo `json:"m.relates_to,omitempty"`
+ Mentions *Mentions `json:"m.mentions,omitempty"`
+}
+
+type OlmCiphertexts map[id.Curve25519]struct {
+ Body string `json:"body"`
+ Type id.OlmMsgType `json:"type"`
+}
+
+type serializableEncryptedEventContent EncryptedEventContent
+
+func (content *EncryptedEventContent) UnmarshalJSON(data []byte) error {
+ err := json.Unmarshal(data, (*serializableEncryptedEventContent)(content))
+ if err != nil {
+ return err
+ }
+ switch content.Algorithm {
+ case id.AlgorithmOlmV1:
+ content.OlmCiphertext = make(OlmCiphertexts)
+ return json.Unmarshal(content.Ciphertext, &content.OlmCiphertext)
+ case id.AlgorithmMegolmV1:
+ if len(content.Ciphertext) == 0 || content.Ciphertext[0] != '"' || content.Ciphertext[len(content.Ciphertext)-1] != '"' {
+ return id.InputNotJSONString
+ }
+ content.MegolmCiphertext = content.Ciphertext[1 : len(content.Ciphertext)-1]
+ }
+ return nil
+}
+
+func (content *EncryptedEventContent) MarshalJSON() ([]byte, error) {
+ var err error
+ switch content.Algorithm {
+ case id.AlgorithmOlmV1:
+ content.Ciphertext, err = json.Marshal(content.OlmCiphertext)
+ case id.AlgorithmMegolmV1:
+ content.Ciphertext = make([]byte, len(content.MegolmCiphertext)+2)
+ content.Ciphertext[0] = '"'
+ content.Ciphertext[len(content.Ciphertext)-1] = '"'
+ copy(content.Ciphertext[1:len(content.Ciphertext)-1], content.MegolmCiphertext)
+ }
+ if err != nil {
+ return nil, err
+ }
+ return json.Marshal((*serializableEncryptedEventContent)(content))
+}
+
+// RoomKeyEventContent represents the content of a m.room_key to_device event.
+// https://spec.matrix.org/v1.2/client-server-api/#mroom_key
+type RoomKeyEventContent struct {
+ Algorithm id.Algorithm `json:"algorithm"`
+ RoomID id.RoomID `json:"room_id"`
+ SessionID id.SessionID `json:"session_id"`
+ SessionKey string `json:"session_key"`
+}
+
+// ForwardedRoomKeyEventContent represents the content of a m.forwarded_room_key to_device event.
+// https://spec.matrix.org/v1.2/client-server-api/#mforwarded_room_key
+type ForwardedRoomKeyEventContent struct {
+ RoomKeyEventContent
+ SenderKey id.SenderKey `json:"sender_key"`
+ SenderClaimedKey id.Ed25519 `json:"sender_claimed_ed25519_key"`
+ ForwardingKeyChain []string `json:"forwarding_curve25519_key_chain"`
+}
+
+type KeyRequestAction string
+
+const (
+ KeyRequestActionRequest = "request"
+ KeyRequestActionCancel = "request_cancellation"
+)
+
+// RoomKeyRequestEventContent represents the content of a m.room_key_request to_device event.
+// https://spec.matrix.org/v1.2/client-server-api/#mroom_key_request
+type RoomKeyRequestEventContent struct {
+ Body RequestedKeyInfo `json:"body"`
+ Action KeyRequestAction `json:"action"`
+ RequestingDeviceID id.DeviceID `json:"requesting_device_id"`
+ RequestID string `json:"request_id"`
+}
+
+type RequestedKeyInfo struct {
+ Algorithm id.Algorithm `json:"algorithm"`
+ RoomID id.RoomID `json:"room_id"`
+ SenderKey id.SenderKey `json:"sender_key"`
+ SessionID id.SessionID `json:"session_id"`
+}
+
+type RoomKeyWithheldCode string
+
+const (
+ RoomKeyWithheldBlacklisted RoomKeyWithheldCode = "m.blacklisted"
+ RoomKeyWithheldUnverified RoomKeyWithheldCode = "m.unverified"
+ RoomKeyWithheldUnauthorized RoomKeyWithheldCode = "m.unauthorised"
+ RoomKeyWithheldUnavailable RoomKeyWithheldCode = "m.unavailable"
+ RoomKeyWithheldNoOlmSession RoomKeyWithheldCode = "m.no_olm"
+)
+
+type RoomKeyWithheldEventContent struct {
+ RoomID id.RoomID `json:"room_id,omitempty"`
+ Algorithm id.Algorithm `json:"algorithm"`
+ SessionID id.SessionID `json:"session_id,omitempty"`
+ SenderKey id.SenderKey `json:"sender_key"`
+ Code RoomKeyWithheldCode `json:"code"`
+ Reason string `json:"reason,omitempty"`
+}
+
+type DummyEventContent struct{}
diff --git a/vendor/maunium.net/go/mautrix/event/ephemeral.go b/vendor/maunium.net/go/mautrix/event/ephemeral.go
new file mode 100644
index 00000000..f447404b
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/event/ephemeral.go
@@ -0,0 +1,140 @@
+// Copyright (c) 2020 Tulir Asokan
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package event
+
+import (
+ "encoding/json"
+ "time"
+
+ "maunium.net/go/mautrix/id"
+)
+
+// TypingEventContent represents the content of a m.typing ephemeral event.
+// https://spec.matrix.org/v1.2/client-server-api/#mtyping
+type TypingEventContent struct {
+ UserIDs []id.UserID `json:"user_ids"`
+}
+
+// ReceiptEventContent represents the content of a m.receipt ephemeral event.
+// https://spec.matrix.org/v1.2/client-server-api/#mreceipt
+type ReceiptEventContent map[id.EventID]Receipts
+
+func (rec ReceiptEventContent) Set(evtID id.EventID, receiptType ReceiptType, userID id.UserID, receipt ReadReceipt) {
+ rec.GetOrCreate(evtID).GetOrCreate(receiptType).Set(userID, receipt)
+}
+
+func (rec ReceiptEventContent) GetOrCreate(evt id.EventID) Receipts {
+ receipts, ok := rec[evt]
+ if !ok {
+ receipts = make(Receipts)
+ rec[evt] = receipts
+ }
+ return receipts
+}
+
+type ReceiptType string
+
+const (
+ ReceiptTypeRead ReceiptType = "m.read"
+ ReceiptTypeReadPrivate ReceiptType = "m.read.private"
+)
+
+type Receipts map[ReceiptType]UserReceipts
+
+func (rps Receipts) GetOrCreate(receiptType ReceiptType) UserReceipts {
+ read, ok := rps[receiptType]
+ if !ok {
+ read = make(UserReceipts)
+ rps[receiptType] = read
+ }
+ return read
+}
+
+type UserReceipts map[id.UserID]ReadReceipt
+
+func (ur UserReceipts) Set(userID id.UserID, receipt ReadReceipt) {
+ ur[userID] = receipt
+}
+
+type ThreadID = id.EventID
+
+const ReadReceiptThreadMain ThreadID = "main"
+
+type ReadReceipt struct {
+ Timestamp time.Time
+
+ // Thread ID for thread-specific read receipts from MSC3771
+ ThreadID ThreadID
+
+ // Extra contains any unknown fields in the read receipt event.
+ // Most servers don't allow clients to set them, so this will be empty in most cases.
+ Extra map[string]interface{}
+}
+
+func (rr *ReadReceipt) UnmarshalJSON(data []byte) error {
+ // Hacky compatibility hack against crappy clients that send double-encoded read receipts.
+ // TODO is this actually needed? clients can't currently set custom content in receipts 🤔
+ if data[0] == '"' && data[len(data)-1] == '"' {
+ var strData string
+ err := json.Unmarshal(data, &strData)
+ if err != nil {
+ return err
+ }
+ data = []byte(strData)
+ }
+
+ var parsed map[string]interface{}
+ err := json.Unmarshal(data, &parsed)
+ if err != nil {
+ return err
+ }
+ threadID, _ := parsed["thread_id"].(string)
+ ts, tsOK := parsed["ts"].(float64)
+ delete(parsed, "thread_id")
+ delete(parsed, "ts")
+ *rr = ReadReceipt{
+ ThreadID: ThreadID(threadID),
+ Extra: parsed,
+ }
+ if tsOK {
+ rr.Timestamp = time.UnixMilli(int64(ts))
+ }
+ return nil
+}
+
+func (rr ReadReceipt) MarshalJSON() ([]byte, error) {
+ data := rr.Extra
+ if data == nil {
+ data = make(map[string]interface{})
+ }
+ if rr.ThreadID != "" {
+ data["thread_id"] = rr.ThreadID
+ }
+ if !rr.Timestamp.IsZero() {
+ data["ts"] = rr.Timestamp.UnixMilli()
+ }
+ return json.Marshal(data)
+}
+
+type Presence string
+
+const (
+ PresenceOnline Presence = "online"
+ PresenceOffline Presence = "offline"
+ PresenceUnavailable Presence = "unavailable"
+)
+
+// PresenceEventContent represents the content of a m.presence ephemeral event.
+// https://spec.matrix.org/v1.2/client-server-api/#mpresence
+type PresenceEventContent struct {
+ Presence Presence `json:"presence"`
+ Displayname string `json:"displayname,omitempty"`
+ AvatarURL id.ContentURIString `json:"avatar_url,omitempty"`
+ LastActiveAgo int64 `json:"last_active_ago,omitempty"`
+ CurrentlyActive bool `json:"currently_active,omitempty"`
+ StatusMessage string `json:"status_msg,omitempty"`
+}
diff --git a/vendor/maunium.net/go/mautrix/event/events.go b/vendor/maunium.net/go/mautrix/event/events.go
new file mode 100644
index 00000000..d2b61046
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/event/events.go
@@ -0,0 +1,149 @@
+// Copyright (c) 2020 Tulir Asokan
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package event
+
+import (
+ "encoding/json"
+ "time"
+
+ "maunium.net/go/mautrix/id"
+)
+
+// 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 id.UserID `json:"sender,omitempty"` // The user ID of the sender of the event
+ Type Type `json:"type"` // The event type
+ Timestamp int64 `json:"origin_server_ts,omitempty"` // The unix timestamp when this message was sent by the origin server
+ ID id.EventID `json:"event_id,omitempty"` // The unique ID of this event
+ RoomID id.RoomID `json:"room_id,omitempty"` // The room the event was sent to. May be nil (e.g. for presence)
+ Content Content `json:"content"` // The JSON content of the event.
+ Redacts id.EventID `json:"redacts,omitempty"` // The event ID that was redacted if a m.room.redaction event
+ Unsigned Unsigned `json:"unsigned,omitempty"` // Unsigned content set by own homeserver.
+
+ Mautrix MautrixInfo `json:"-"`
+
+ ToUserID id.UserID `json:"to_user_id,omitempty"` // The user ID that the to-device event was sent to. Only present in MSC2409 appservice transactions.
+ ToDeviceID id.DeviceID `json:"to_device_id,omitempty"` // The device ID that the to-device event was sent to. Only present in MSC2409 appservice transactions.
+}
+
+type eventForMarshaling struct {
+ StateKey *string `json:"state_key,omitempty"`
+ Sender id.UserID `json:"sender,omitempty"`
+ Type Type `json:"type"`
+ Timestamp int64 `json:"origin_server_ts,omitempty"`
+ ID id.EventID `json:"event_id,omitempty"`
+ RoomID id.RoomID `json:"room_id,omitempty"`
+ Content Content `json:"content"`
+ Redacts id.EventID `json:"redacts,omitempty"`
+ Unsigned *Unsigned `json:"unsigned,omitempty"`
+
+ PrevContent *Content `json:"prev_content,omitempty"`
+ ReplacesState *id.EventID `json:"replaces_state,omitempty"`
+
+ ToUserID id.UserID `json:"to_user_id,omitempty"`
+ ToDeviceID id.DeviceID `json:"to_device_id,omitempty"`
+}
+
+// UnmarshalJSON unmarshals the event, including moving prev_content from the top level to inside unsigned.
+func (evt *Event) UnmarshalJSON(data []byte) error {
+ var efm eventForMarshaling
+ err := json.Unmarshal(data, &efm)
+ if err != nil {
+ return err
+ }
+ evt.StateKey = efm.StateKey
+ evt.Sender = efm.Sender
+ evt.Type = efm.Type
+ evt.Timestamp = efm.Timestamp
+ evt.ID = efm.ID
+ evt.RoomID = efm.RoomID
+ evt.Content = efm.Content
+ evt.Redacts = efm.Redacts
+ if efm.Unsigned != nil {
+ evt.Unsigned = *efm.Unsigned
+ }
+ if efm.PrevContent != nil && evt.Unsigned.PrevContent == nil {
+ evt.Unsigned.PrevContent = efm.PrevContent
+ }
+ if efm.ReplacesState != nil && *efm.ReplacesState != "" && evt.Unsigned.ReplacesState == "" {
+ evt.Unsigned.ReplacesState = *efm.ReplacesState
+ }
+ evt.ToUserID = efm.ToUserID
+ evt.ToDeviceID = efm.ToDeviceID
+ return nil
+}
+
+// MarshalJSON marshals the event, including omitting the unsigned field if it's empty.
+//
+// This is necessary because Unsigned is not a pointer (for convenience reasons),
+// and encoding/json doesn't know how to check if a non-pointer struct is empty.
+//
+// TODO(tulir): maybe it makes more sense to make Unsigned a pointer and make an easy and safe way to access it?
+func (evt *Event) MarshalJSON() ([]byte, error) {
+ unsigned := &evt.Unsigned
+ if unsigned.IsEmpty() {
+ unsigned = nil
+ }
+ return json.Marshal(&eventForMarshaling{
+ StateKey: evt.StateKey,
+ Sender: evt.Sender,
+ Type: evt.Type,
+ Timestamp: evt.Timestamp,
+ ID: evt.ID,
+ RoomID: evt.RoomID,
+ Content: evt.Content,
+ Redacts: evt.Redacts,
+ Unsigned: unsigned,
+ ToUserID: evt.ToUserID,
+ ToDeviceID: evt.ToDeviceID,
+ })
+}
+
+type MautrixInfo struct {
+ TrustState id.TrustState
+ ForwardedKeys bool
+ WasEncrypted bool
+ TrustSource *id.Device
+
+ ReceivedAt time.Time
+ DecryptionDuration time.Duration
+
+ CheckpointSent bool
+}
+
+func (evt *Event) GetStateKey() string {
+ if evt.StateKey != nil {
+ return *evt.StateKey
+ }
+ return ""
+}
+
+type StrippedState struct {
+ Content Content `json:"content"`
+ Type Type `json:"type"`
+ StateKey string `json:"state_key"`
+ Sender id.UserID `json:"sender"`
+}
+
+type Unsigned struct {
+ PrevContent *Content `json:"prev_content,omitempty"`
+ PrevSender id.UserID `json:"prev_sender,omitempty"`
+ ReplacesState id.EventID `json:"replaces_state,omitempty"`
+ Age int64 `json:"age,omitempty"`
+ TransactionID string `json:"transaction_id,omitempty"`
+ Relations *Relations `json:"m.relations,omitempty"`
+ RedactedBecause *Event `json:"redacted_because,omitempty"`
+ InviteRoomState []StrippedState `json:"invite_room_state,omitempty"`
+
+ BeeperHSOrder int64 `json:"com.beeper.hs.order,omitempty"`
+}
+
+func (us *Unsigned) IsEmpty() bool {
+ return us.PrevContent == nil && us.PrevSender == "" && us.ReplacesState == "" && us.Age == 0 &&
+ us.TransactionID == "" && us.RedactedBecause == nil && us.InviteRoomState == nil && us.Relations == nil
+}
diff --git a/vendor/maunium.net/go/mautrix/event/member.go b/vendor/maunium.net/go/mautrix/event/member.go
new file mode 100644
index 00000000..ebafdcb7
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/event/member.go
@@ -0,0 +1,53 @@
+// Copyright (c) 2020 Tulir Asokan
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package event
+
+import (
+ "encoding/json"
+
+ "maunium.net/go/mautrix/id"
+)
+
+// Membership is an enum specifying the membership state of a room member.
+type Membership string
+
+func (ms Membership) IsInviteOrJoin() bool {
+ return ms == MembershipJoin || ms == MembershipInvite
+}
+
+func (ms Membership) IsLeaveOrBan() bool {
+ return ms == MembershipLeave || ms == MembershipBan
+}
+
+// The allowed membership states as specified in spec section 10.5.5.
+const (
+ MembershipJoin Membership = "join"
+ MembershipLeave Membership = "leave"
+ MembershipInvite Membership = "invite"
+ MembershipBan Membership = "ban"
+ MembershipKnock Membership = "knock"
+)
+
+// MemberEventContent represents the content of a m.room.member state event.
+// https://spec.matrix.org/v1.2/client-server-api/#mroommember
+type MemberEventContent struct {
+ Membership Membership `json:"membership"`
+ AvatarURL id.ContentURIString `json:"avatar_url,omitempty"`
+ Displayname string `json:"displayname,omitempty"`
+ IsDirect bool `json:"is_direct,omitempty"`
+ ThirdPartyInvite *ThirdPartyInvite `json:"third_party_invite,omitempty"`
+ Reason string `json:"reason,omitempty"`
+}
+
+type ThirdPartyInvite struct {
+ DisplayName string `json:"display_name"`
+ Signed struct {
+ Token string `json:"token"`
+ Signatures json.RawMessage `json:"signatures"`
+ MXID string `json:"mxid"`
+ }
+}
diff --git a/vendor/maunium.net/go/mautrix/event/message.go b/vendor/maunium.net/go/mautrix/event/message.go
new file mode 100644
index 00000000..a5f5ec0e
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/event/message.go
@@ -0,0 +1,276 @@
+// Copyright (c) 2023 Tulir Asokan
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package event
+
+import (
+ "encoding/json"
+ "strconv"
+ "strings"
+
+ "golang.org/x/net/html"
+
+ "maunium.net/go/mautrix/crypto/attachment"
+ "maunium.net/go/mautrix/id"
+)
+
+// MessageType is the sub-type of a m.room.message event.
+// https://spec.matrix.org/v1.2/client-server-api/#mroommessage-msgtypes
+type MessageType string
+
+// Msgtypes
+const (
+ MsgText MessageType = "m.text"
+ MsgEmote MessageType = "m.emote"
+ MsgNotice MessageType = "m.notice"
+ MsgImage MessageType = "m.image"
+ MsgLocation MessageType = "m.location"
+ MsgVideo MessageType = "m.video"
+ MsgAudio MessageType = "m.audio"
+ MsgFile MessageType = "m.file"
+
+ MsgVerificationRequest MessageType = "m.key.verification.request"
+)
+
+// Format specifies the format of the formatted_body in m.room.message events.
+// https://spec.matrix.org/v1.2/client-server-api/#mroommessage-msgtypes
+type Format string
+
+// Message formats
+const (
+ FormatHTML Format = "org.matrix.custom.html"
+)
+
+// RedactionEventContent represents the content of a m.room.redaction message event.
+//
+// The redacted event ID is still at the top level, but will move in a future room version.
+// See https://github.com/matrix-org/matrix-doc/pull/2244 and https://github.com/matrix-org/matrix-doc/pull/2174
+//
+// https://spec.matrix.org/v1.2/client-server-api/#mroomredaction
+type RedactionEventContent struct {
+ Reason string `json:"reason,omitempty"`
+}
+
+// ReactionEventContent represents the content of a m.reaction message event.
+// This is not yet in a spec release, see https://github.com/matrix-org/matrix-doc/pull/1849
+type ReactionEventContent struct {
+ RelatesTo RelatesTo `json:"m.relates_to"`
+}
+
+func (content *ReactionEventContent) GetRelatesTo() *RelatesTo {
+ return &content.RelatesTo
+}
+
+func (content *ReactionEventContent) OptionalGetRelatesTo() *RelatesTo {
+ return &content.RelatesTo
+}
+
+func (content *ReactionEventContent) SetRelatesTo(rel *RelatesTo) {
+ content.RelatesTo = *rel
+}
+
+// MessageEventContent represents the content of a m.room.message event.
+//
+// It is also used to represent m.sticker events, as they are equivalent to m.room.message
+// with the exception of the msgtype field.
+//
+// https://spec.matrix.org/v1.2/client-server-api/#mroommessage
+type MessageEventContent struct {
+ // Base m.room.message fields
+ MsgType MessageType `json:"msgtype,omitempty"`
+ Body string `json:"body"`
+
+ // Extra fields for text types
+ Format Format `json:"format,omitempty"`
+ FormattedBody string `json:"formatted_body,omitempty"`
+
+ // Extra field for m.location
+ GeoURI string `json:"geo_uri,omitempty"`
+
+ // Extra fields for media types
+ URL id.ContentURIString `json:"url,omitempty"`
+ Info *FileInfo `json:"info,omitempty"`
+ File *EncryptedFileInfo `json:"file,omitempty"`
+
+ FileName string `json:"filename,omitempty"`
+
+ Mentions *Mentions `json:"m.mentions,omitempty"`
+ UnstableMentions *Mentions `json:"org.matrix.msc3952.mentions,omitempty"`
+
+ // Edits and relations
+ NewContent *MessageEventContent `json:"m.new_content,omitempty"`
+ RelatesTo *RelatesTo `json:"m.relates_to,omitempty"`
+
+ // In-room verification
+ To id.UserID `json:"to,omitempty"`
+ FromDevice id.DeviceID `json:"from_device,omitempty"`
+ Methods []VerificationMethod `json:"methods,omitempty"`
+
+ replyFallbackRemoved bool
+
+ MessageSendRetry *BeeperRetryMetadata `json:"com.beeper.message_send_retry,omitempty"`
+}
+
+func (content *MessageEventContent) GetRelatesTo() *RelatesTo {
+ if content.RelatesTo == nil {
+ content.RelatesTo = &RelatesTo{}
+ }
+ return content.RelatesTo
+}
+
+func (content *MessageEventContent) OptionalGetRelatesTo() *RelatesTo {
+ return content.RelatesTo
+}
+
+func (content *MessageEventContent) SetRelatesTo(rel *RelatesTo) {
+ content.RelatesTo = rel
+}
+
+func (content *MessageEventContent) SetEdit(original id.EventID) {
+ newContent := *content
+ content.NewContent = &newContent
+ content.RelatesTo = (&RelatesTo{}).SetReplace(original)
+ if content.MsgType == MsgText || content.MsgType == MsgNotice {
+ content.Body = "* " + content.Body
+ if content.Format == FormatHTML && len(content.FormattedBody) > 0 {
+ content.FormattedBody = "* " + content.FormattedBody
+ }
+ // If the message is long, remove most of the useless edit fallback to avoid event size issues.
+ if len(content.Body) > 10000 {
+ content.FormattedBody = ""
+ content.Format = ""
+ content.Body = content.Body[:50] + "[edit fallback cut…]"
+ }
+ }
+}
+
+func TextToHTML(text string) string {
+ return strings.ReplaceAll(html.EscapeString(text), "\n", "<br/>")
+}
+
+func (content *MessageEventContent) EnsureHasHTML() {
+ if len(content.FormattedBody) == 0 || content.Format != FormatHTML {
+ content.FormattedBody = TextToHTML(content.Body)
+ content.Format = FormatHTML
+ }
+}
+
+func (content *MessageEventContent) GetFile() *EncryptedFileInfo {
+ if content.File == nil {
+ content.File = &EncryptedFileInfo{}
+ }
+ return content.File
+}
+
+func (content *MessageEventContent) GetInfo() *FileInfo {
+ if content.Info == nil {
+ content.Info = &FileInfo{}
+ }
+ return content.Info
+}
+
+type Mentions struct {
+ UserIDs []id.UserID `json:"user_ids,omitempty"`
+ Room bool `json:"room,omitempty"`
+}
+
+type EncryptedFileInfo struct {
+ attachment.EncryptedFile
+ URL id.ContentURIString `json:"url"`
+}
+
+type FileInfo struct {
+ MimeType string `json:"mimetype,omitempty"`
+ ThumbnailInfo *FileInfo `json:"thumbnail_info,omitempty"`
+ ThumbnailURL id.ContentURIString `json:"thumbnail_url,omitempty"`
+ ThumbnailFile *EncryptedFileInfo `json:"thumbnail_file,omitempty"`
+ Width int `json:"-"`
+ Height int `json:"-"`
+ Duration int `json:"-"`
+ Size int `json:"-"`
+}
+
+type serializableFileInfo struct {
+ MimeType string `json:"mimetype,omitempty"`
+ ThumbnailInfo *serializableFileInfo `json:"thumbnail_info,omitempty"`
+ ThumbnailURL id.ContentURIString `json:"thumbnail_url,omitempty"`
+ ThumbnailFile *EncryptedFileInfo `json:"thumbnail_file,omitempty"`
+
+ Width json.Number `json:"w,omitempty"`
+ Height json.Number `json:"h,omitempty"`
+ Duration json.Number `json:"duration,omitempty"`
+ Size json.Number `json:"size,omitempty"`
+}
+
+func (sfi *serializableFileInfo) CopyFrom(fileInfo *FileInfo) *serializableFileInfo {
+ if fileInfo == nil {
+ return nil
+ }
+ *sfi = serializableFileInfo{
+ MimeType: fileInfo.MimeType,
+ ThumbnailURL: fileInfo.ThumbnailURL,
+ ThumbnailInfo: (&serializableFileInfo{}).CopyFrom(fileInfo.ThumbnailInfo),
+ ThumbnailFile: fileInfo.ThumbnailFile,
+ }
+ if fileInfo.Width > 0 {
+ sfi.Width = json.Number(strconv.Itoa(fileInfo.Width))
+ }
+ if fileInfo.Height > 0 {
+ sfi.Height = json.Number(strconv.Itoa(fileInfo.Height))
+ }
+ if fileInfo.Size > 0 {
+ sfi.Size = json.Number(strconv.Itoa(fileInfo.Size))
+
+ }
+ if fileInfo.Duration > 0 {
+ sfi.Duration = json.Number(strconv.Itoa(int(fileInfo.Duration)))
+ }
+ return sfi
+}
+
+func (sfi *serializableFileInfo) CopyTo(fileInfo *FileInfo) {
+ *fileInfo = FileInfo{
+ Width: numberToInt(sfi.Width),
+ Height: numberToInt(sfi.Height),
+ Size: numberToInt(sfi.Size),
+ Duration: numberToInt(sfi.Duration),
+ MimeType: sfi.MimeType,
+ ThumbnailURL: sfi.ThumbnailURL,
+ ThumbnailFile: sfi.ThumbnailFile,
+ }
+ if sfi.ThumbnailInfo != nil {
+ fileInfo.ThumbnailInfo = &FileInfo{}
+ sfi.ThumbnailInfo.CopyTo(fileInfo.ThumbnailInfo)
+ }
+}
+
+func (fileInfo *FileInfo) UnmarshalJSON(data []byte) error {
+ sfi := &serializableFileInfo{}
+ if err := json.Unmarshal(data, sfi); err != nil {
+ return err
+ }
+ sfi.CopyTo(fileInfo)
+ return nil
+}
+
+func (fileInfo *FileInfo) MarshalJSON() ([]byte, error) {
+ return json.Marshal((&serializableFileInfo{}).CopyFrom(fileInfo))
+}
+
+func numberToInt(val json.Number) int {
+ f64, _ := val.Float64()
+ if f64 > 0 {
+ return int(f64)
+ }
+ return 0
+}
+
+func (fileInfo *FileInfo) GetThumbnailInfo() *FileInfo {
+ if fileInfo.ThumbnailInfo == nil {
+ fileInfo.ThumbnailInfo = &FileInfo{}
+ }
+ return fileInfo.ThumbnailInfo
+}
diff --git a/vendor/maunium.net/go/mautrix/event/powerlevels.go b/vendor/maunium.net/go/mautrix/event/powerlevels.go
new file mode 100644
index 00000000..cf623565
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/event/powerlevels.go
@@ -0,0 +1,149 @@
+// Copyright (c) 2020 Tulir Asokan
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package event
+
+import (
+ "sync"
+
+ "maunium.net/go/mautrix/id"
+)
+
+// PowerLevelsEventContent represents the content of a m.room.power_levels state event content.
+// https://spec.matrix.org/v1.5/client-server-api/#mroompower_levels
+type PowerLevelsEventContent struct {
+ usersLock sync.RWMutex
+ Users map[id.UserID]int `json:"users,omitempty"`
+ UsersDefault int `json:"users_default,omitempty"`
+
+ eventsLock sync.RWMutex
+ Events map[string]int `json:"events,omitempty"`
+ EventsDefault int `json:"events_default,omitempty"`
+
+ Notifications *NotificationPowerLevels `json:"notifications,omitempty"`
+
+ StateDefaultPtr *int `json:"state_default,omitempty"`
+
+ InvitePtr *int `json:"invite,omitempty"`
+ KickPtr *int `json:"kick,omitempty"`
+ BanPtr *int `json:"ban,omitempty"`
+ RedactPtr *int `json:"redact,omitempty"`
+ HistoricalPtr *int `json:"historical,omitempty"`
+}
+
+type NotificationPowerLevels struct {
+ RoomPtr *int `json:"room,omitempty"`
+}
+
+func (npl *NotificationPowerLevels) Room() int {
+ if npl != nil && npl.RoomPtr != nil {
+ return *npl.RoomPtr
+ }
+ return 50
+}
+
+func (pl *PowerLevelsEventContent) Invite() int {
+ if pl.InvitePtr != nil {
+ return *pl.InvitePtr
+ }
+ return 50
+}
+
+func (pl *PowerLevelsEventContent) Kick() int {
+ if pl.KickPtr != nil {
+ return *pl.KickPtr
+ }
+ return 50
+}
+
+func (pl *PowerLevelsEventContent) Ban() int {
+ if pl.BanPtr != nil {
+ return *pl.BanPtr
+ }
+ return 50
+}
+
+func (pl *PowerLevelsEventContent) Redact() int {
+ if pl.RedactPtr != nil {
+ return *pl.RedactPtr
+ }
+ return 50
+}
+
+func (pl *PowerLevelsEventContent) Historical() int {
+ if pl.HistoricalPtr != nil {
+ return *pl.HistoricalPtr
+ }
+ return 100
+}
+
+func (pl *PowerLevelsEventContent) StateDefault() int {
+ if pl.StateDefaultPtr != nil {
+ return *pl.StateDefaultPtr
+ }
+ return 50
+}
+
+func (pl *PowerLevelsEventContent) GetUserLevel(userID id.UserID) int {
+ pl.usersLock.RLock()
+ defer pl.usersLock.RUnlock()
+ level, ok := pl.Users[userID]
+ if !ok {
+ return pl.UsersDefault
+ }
+ return level
+}
+
+func (pl *PowerLevelsEventContent) SetUserLevel(userID id.UserID, level int) {
+ pl.usersLock.Lock()
+ defer pl.usersLock.Unlock()
+ if level == pl.UsersDefault {
+ delete(pl.Users, userID)
+ } else {
+ pl.Users[userID] = level
+ }
+}
+
+func (pl *PowerLevelsEventContent) EnsureUserLevel(userID id.UserID, level int) bool {
+ existingLevel := pl.GetUserLevel(userID)
+ if existingLevel != level {
+ pl.SetUserLevel(userID, level)
+ return true
+ }
+ return false
+}
+
+func (pl *PowerLevelsEventContent) GetEventLevel(eventType Type) int {
+ pl.eventsLock.RLock()
+ defer pl.eventsLock.RUnlock()
+ level, ok := pl.Events[eventType.String()]
+ if !ok {
+ if eventType.IsState() {
+ return pl.StateDefault()
+ }
+ return pl.EventsDefault
+ }
+ return level
+}
+
+func (pl *PowerLevelsEventContent) SetEventLevel(eventType Type, level int) {
+ pl.eventsLock.Lock()
+ defer pl.eventsLock.Unlock()
+ if (eventType.IsState() && level == pl.StateDefault()) || (!eventType.IsState() && level == pl.EventsDefault) {
+ delete(pl.Events, eventType.String())
+ } else {
+ pl.Events[eventType.String()] = level
+ }
+}
+
+func (pl *PowerLevelsEventContent) EnsureEventLevel(eventType Type, level int) bool {
+ existingLevel := pl.GetEventLevel(eventType)
+ if existingLevel != level {
+ pl.SetEventLevel(eventType, level)
+ return true
+ }
+ return false
+}
diff --git a/vendor/maunium.net/go/mautrix/event/relations.go b/vendor/maunium.net/go/mautrix/event/relations.go
new file mode 100644
index 00000000..ecd7a959
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/event/relations.go
@@ -0,0 +1,234 @@
+// Copyright (c) 2020 Tulir Asokan
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package event
+
+import (
+ "encoding/json"
+
+ "maunium.net/go/mautrix/id"
+)
+
+type RelationType string
+
+const (
+ RelReplace RelationType = "m.replace"
+ RelReference RelationType = "m.reference"
+ RelAnnotation RelationType = "m.annotation"
+ RelThread RelationType = "m.thread"
+)
+
+type RelatesTo struct {
+ Type RelationType `json:"rel_type,omitempty"`
+ EventID id.EventID `json:"event_id,omitempty"`
+ Key string `json:"key,omitempty"`
+
+ InReplyTo *InReplyTo `json:"m.in_reply_to,omitempty"`
+ IsFallingBack bool `json:"is_falling_back,omitempty"`
+}
+
+type InReplyTo struct {
+ EventID id.EventID `json:"event_id,omitempty"`
+
+ UnstableRoomID id.RoomID `json:"room_id,omitempty"`
+}
+
+func (rel *RelatesTo) Copy() *RelatesTo {
+ if rel == nil {
+ return nil
+ }
+ cp := *rel
+ return &cp
+}
+
+func (rel *RelatesTo) GetReplaceID() id.EventID {
+ if rel != nil && rel.Type == RelReplace {
+ return rel.EventID
+ }
+ return ""
+}
+
+func (rel *RelatesTo) GetReferenceID() id.EventID {
+ if rel != nil && rel.Type == RelReference {
+ return rel.EventID
+ }
+ return ""
+}
+
+func (rel *RelatesTo) GetThreadParent() id.EventID {
+ if rel != nil && rel.Type == RelThread {
+ return rel.EventID
+ }
+ return ""
+}
+
+func (rel *RelatesTo) GetReplyTo() id.EventID {
+ if rel != nil && rel.InReplyTo != nil {
+ return rel.InReplyTo.EventID
+ }
+ return ""
+}
+
+func (rel *RelatesTo) GetNonFallbackReplyTo() id.EventID {
+ if rel != nil && rel.InReplyTo != nil && !rel.IsFallingBack {
+ return rel.InReplyTo.EventID
+ }
+ return ""
+}
+
+func (rel *RelatesTo) GetAnnotationID() id.EventID {
+ if rel != nil && rel.Type == RelAnnotation {
+ return rel.EventID
+ }
+ return ""
+}
+
+func (rel *RelatesTo) GetAnnotationKey() string {
+ if rel != nil && rel.Type == RelAnnotation {
+ return rel.Key
+ }
+ return ""
+}
+
+func (rel *RelatesTo) SetReplace(mxid id.EventID) *RelatesTo {
+ rel.Type = RelReplace
+ rel.EventID = mxid
+ return rel
+}
+
+func (rel *RelatesTo) SetReplyTo(mxid id.EventID) *RelatesTo {
+ rel.InReplyTo = &InReplyTo{EventID: mxid}
+ rel.IsFallingBack = false
+ return rel
+}
+
+func (rel *RelatesTo) SetThread(mxid, fallback id.EventID) *RelatesTo {
+ rel.Type = RelThread
+ rel.EventID = mxid
+ if fallback != "" && rel.GetReplyTo() == "" {
+ rel.SetReplyTo(fallback)
+ rel.IsFallingBack = true
+ }
+ return rel
+}
+
+func (rel *RelatesTo) SetAnnotation(mxid id.EventID, key string) *RelatesTo {
+ rel.Type = RelAnnotation
+ rel.EventID = mxid
+ rel.Key = key
+ return rel
+}
+
+type RelationChunkItem struct {
+ Type RelationType `json:"type"`
+ EventID string `json:"event_id,omitempty"`
+ Key string `json:"key,omitempty"`
+ Count int `json:"count,omitempty"`
+}
+
+type RelationChunk struct {
+ Chunk []RelationChunkItem `json:"chunk"`
+
+ Limited bool `json:"limited"`
+ Count int `json:"count"`
+}
+
+type AnnotationChunk struct {
+ RelationChunk
+ Map map[string]int `json:"-"`
+}
+
+type serializableAnnotationChunk AnnotationChunk
+
+func (ac *AnnotationChunk) UnmarshalJSON(data []byte) error {
+ if err := json.Unmarshal(data, (*serializableAnnotationChunk)(ac)); err != nil {
+ return err
+ }
+ ac.Map = make(map[string]int)
+ for _, item := range ac.Chunk {
+ if item.Key != "" {
+ ac.Map[item.Key] += item.Count
+ }
+ }
+ return nil
+}
+
+func (ac *AnnotationChunk) Serialize() RelationChunk {
+ ac.Chunk = make([]RelationChunkItem, len(ac.Map))
+ i := 0
+ for key, count := range ac.Map {
+ ac.Chunk[i] = RelationChunkItem{
+ Type: RelAnnotation,
+ Key: key,
+ Count: count,
+ }
+ i++
+ }
+ return ac.RelationChunk
+}
+
+type EventIDChunk struct {
+ RelationChunk
+ List []string `json:"-"`
+}
+
+type serializableEventIDChunk EventIDChunk
+
+func (ec *EventIDChunk) UnmarshalJSON(data []byte) error {
+ if err := json.Unmarshal(data, (*serializableEventIDChunk)(ec)); err != nil {
+ return err
+ }
+ for _, item := range ec.Chunk {
+ ec.List = append(ec.List, item.EventID)
+ }
+ return nil
+}
+
+func (ec *EventIDChunk) Serialize(typ RelationType) RelationChunk {
+ ec.Chunk = make([]RelationChunkItem, len(ec.List))
+ for i, eventID := range ec.List {
+ ec.Chunk[i] = RelationChunkItem{
+ Type: typ,
+ EventID: eventID,
+ }
+ }
+ return ec.RelationChunk
+}
+
+type Relations struct {
+ Raw map[RelationType]RelationChunk `json:"-"`
+
+ Annotations AnnotationChunk `json:"m.annotation,omitempty"`
+ References EventIDChunk `json:"m.reference,omitempty"`
+ Replaces EventIDChunk `json:"m.replace,omitempty"`
+}
+
+type serializableRelations Relations
+
+func (relations *Relations) UnmarshalJSON(data []byte) error {
+ if err := json.Unmarshal(data, &relations.Raw); err != nil {
+ return err
+ }
+ return json.Unmarshal(data, (*serializableRelations)(relations))
+}
+
+func (relations *Relations) MarshalJSON() ([]byte, error) {
+ if relations.Raw == nil {
+ relations.Raw = make(map[RelationType]RelationChunk)
+ }
+ relations.Raw[RelAnnotation] = relations.Annotations.Serialize()
+ relations.Raw[RelReference] = relations.References.Serialize(RelReference)
+ relations.Raw[RelReplace] = relations.Replaces.Serialize(RelReplace)
+ for key, item := range relations.Raw {
+ if !item.Limited {
+ item.Count = len(item.Chunk)
+ }
+ if item.Count == 0 {
+ delete(relations.Raw, key)
+ }
+ }
+ return json.Marshal(relations.Raw)
+}
diff --git a/vendor/maunium.net/go/mautrix/event/reply.go b/vendor/maunium.net/go/mautrix/event/reply.go
new file mode 100644
index 00000000..e1844a4a
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/event/reply.go
@@ -0,0 +1,100 @@
+// Copyright (c) 2020 Tulir Asokan
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package event
+
+import (
+ "fmt"
+ "regexp"
+ "strings"
+
+ "golang.org/x/net/html"
+
+ "maunium.net/go/mautrix/id"
+)
+
+var HTMLReplyFallbackRegex = regexp.MustCompile(`^<mx-reply>[\s\S]+?</mx-reply>`)
+
+func TrimReplyFallbackHTML(html string) string {
+ return HTMLReplyFallbackRegex.ReplaceAllString(html, "")
+}
+
+func TrimReplyFallbackText(text string) string {
+ if !strings.HasPrefix(text, "> ") || !strings.Contains(text, "\n") {
+ return text
+ }
+
+ lines := strings.Split(text, "\n")
+ for len(lines) > 0 && strings.HasPrefix(lines[0], "> ") {
+ lines = lines[1:]
+ }
+ return strings.TrimSpace(strings.Join(lines, "\n"))
+}
+
+func (content *MessageEventContent) RemoveReplyFallback() {
+ if len(content.RelatesTo.GetReplyTo()) > 0 && !content.replyFallbackRemoved {
+ if content.Format == FormatHTML {
+ content.FormattedBody = TrimReplyFallbackHTML(content.FormattedBody)
+ }
+ content.Body = TrimReplyFallbackText(content.Body)
+ content.replyFallbackRemoved = true
+ }
+}
+
+// Deprecated: RelatesTo methods are nil-safe, so RelatesTo.GetReplyTo can be used directly
+func (content *MessageEventContent) GetReplyTo() id.EventID {
+ return content.RelatesTo.GetReplyTo()
+}
+
+const ReplyFormat = `<mx-reply><blockquote><a href="https://matrix.to/#/%s/%s">In reply to</a> <a href="https://matrix.to/#/%s">%s</a><br>%s</blockquote></mx-reply>`
+
+func (evt *Event) GenerateReplyFallbackHTML() string {
+ parsedContent, ok := evt.Content.Parsed.(*MessageEventContent)
+ if !ok {
+ return ""
+ }
+ parsedContent.RemoveReplyFallback()
+ body := parsedContent.FormattedBody
+ if len(body) == 0 {
+ body = strings.ReplaceAll(html.EscapeString(parsedContent.Body), "\n", "<br/>")
+ }
+
+ senderDisplayName := evt.Sender
+
+ return fmt.Sprintf(ReplyFormat, evt.RoomID, evt.ID, evt.Sender, senderDisplayName, body)
+}
+
+func (evt *Event) GenerateReplyFallbackText() string {
+ parsedContent, ok := evt.Content.Parsed.(*MessageEventContent)
+ if !ok {
+ return ""
+ }
+ parsedContent.RemoveReplyFallback()
+ body := parsedContent.Body
+ lines := strings.Split(strings.TrimSpace(body), "\n")
+ firstLine, lines := lines[0], lines[1:]
+
+ senderDisplayName := evt.Sender
+
+ var fallbackText strings.Builder
+ _, _ = fmt.Fprintf(&fallbackText, "> <%s> %s", senderDisplayName, firstLine)
+ for _, line := range lines {
+ _, _ = fmt.Fprintf(&fallbackText, "\n> %s", line)
+ }
+ fallbackText.WriteString("\n\n")
+ return fallbackText.String()
+}
+
+func (content *MessageEventContent) SetReply(inReplyTo *Event) {
+ content.RelatesTo = (&RelatesTo{}).SetReplyTo(inReplyTo.ID)
+
+ if content.MsgType == MsgText || content.MsgType == MsgNotice {
+ content.EnsureHasHTML()
+ content.FormattedBody = inReplyTo.GenerateReplyFallbackHTML() + content.FormattedBody
+ content.Body = inReplyTo.GenerateReplyFallbackText() + content.Body
+ content.replyFallbackRemoved = false
+ }
+}
diff --git a/vendor/maunium.net/go/mautrix/event/state.go b/vendor/maunium.net/go/mautrix/event/state.go
new file mode 100644
index 00000000..a387f015
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/event/state.go
@@ -0,0 +1,176 @@
+// Copyright (c) 2021 Tulir Asokan
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package event
+
+import (
+ "maunium.net/go/mautrix/id"
+)
+
+// CanonicalAliasEventContent represents the content of a m.room.canonical_alias state event.
+// https://spec.matrix.org/v1.2/client-server-api/#mroomcanonical_alias
+type CanonicalAliasEventContent struct {
+ Alias id.RoomAlias `json:"alias"`
+ AltAliases []id.RoomAlias `json:"alt_aliases,omitempty"`
+}
+
+// RoomNameEventContent represents the content of a m.room.name state event.
+// https://spec.matrix.org/v1.2/client-server-api/#mroomname
+type RoomNameEventContent struct {
+ Name string `json:"name"`
+}
+
+// RoomAvatarEventContent represents the content of a m.room.avatar state event.
+// https://spec.matrix.org/v1.2/client-server-api/#mroomavatar
+type RoomAvatarEventContent struct {
+ URL id.ContentURI `json:"url"`
+ Info *FileInfo `json:"info,omitempty"`
+}
+
+// ServerACLEventContent represents the content of a m.room.server_acl state event.
+// https://spec.matrix.org/v1.2/client-server-api/#server-access-control-lists-acls-for-rooms
+type ServerACLEventContent struct {
+ Allow []string `json:"allow,omitempty"`
+ AllowIPLiterals bool `json:"allow_ip_literals"`
+ Deny []string `json:"deny,omitempty"`
+}
+
+// TopicEventContent represents the content of a m.room.topic state event.
+// https://spec.matrix.org/v1.2/client-server-api/#mroomtopic
+type TopicEventContent struct {
+ Topic string `json:"topic"`
+}
+
+// TombstoneEventContent represents the content of a m.room.tombstone state event.
+// https://spec.matrix.org/v1.2/client-server-api/#mroomtombstone
+type TombstoneEventContent struct {
+ Body string `json:"body"`
+ ReplacementRoom id.RoomID `json:"replacement_room"`
+}
+
+type Predecessor struct {
+ RoomID id.RoomID `json:"room_id"`
+ EventID id.EventID `json:"event_id"`
+}
+
+// CreateEventContent represents the content of a m.room.create state event.
+// https://spec.matrix.org/v1.2/client-server-api/#mroomcreate
+type CreateEventContent struct {
+ Type RoomType `json:"type,omitempty"`
+ Creator id.UserID `json:"creator,omitempty"`
+ Federate bool `json:"m.federate,omitempty"`
+ RoomVersion string `json:"room_version,omitempty"`
+ Predecessor *Predecessor `json:"predecessor,omitempty"`
+}
+
+// JoinRule specifies how open a room is to new members.
+// https://spec.matrix.org/v1.2/client-server-api/#mroomjoin_rules
+type JoinRule string
+
+const (
+ JoinRulePublic JoinRule = "public"
+ JoinRuleKnock JoinRule = "knock"
+ JoinRuleInvite JoinRule = "invite"
+ JoinRuleRestricted JoinRule = "restricted"
+ JoinRulePrivate JoinRule = "private"
+)
+
+// JoinRulesEventContent represents the content of a m.room.join_rules state event.
+// https://spec.matrix.org/v1.2/client-server-api/#mroomjoin_rules
+type JoinRulesEventContent struct {
+ JoinRule JoinRule `json:"join_rule"`
+ Allow []JoinRuleAllow `json:"allow,omitempty"`
+}
+
+type JoinRuleAllowType string
+
+const (
+ JoinRuleAllowRoomMembership JoinRuleAllowType = "m.room_membership"
+)
+
+type JoinRuleAllow struct {
+ RoomID id.RoomID `json:"room_id"`
+ Type JoinRuleAllowType `json:"type"`
+}
+
+// PinnedEventsEventContent represents the content of a m.room.pinned_events state event.
+// https://spec.matrix.org/v1.2/client-server-api/#mroompinned_events
+type PinnedEventsEventContent struct {
+ Pinned []id.EventID `json:"pinned"`
+}
+
+// HistoryVisibility specifies who can see new messages.
+// https://spec.matrix.org/v1.2/client-server-api/#mroomhistory_visibility
+type HistoryVisibility string
+
+const (
+ HistoryVisibilityInvited HistoryVisibility = "invited"
+ HistoryVisibilityJoined HistoryVisibility = "joined"
+ HistoryVisibilityShared HistoryVisibility = "shared"
+ HistoryVisibilityWorldReadable HistoryVisibility = "world_readable"
+)
+
+// HistoryVisibilityEventContent represents the content of a m.room.history_visibility state event.
+// https://spec.matrix.org/v1.2/client-server-api/#mroomhistory_visibility
+type HistoryVisibilityEventContent struct {
+ HistoryVisibility HistoryVisibility `json:"history_visibility"`
+}
+
+// GuestAccess specifies whether or not guest accounts can join.
+// https://spec.matrix.org/v1.2/client-server-api/#mroomguest_access
+type GuestAccess string
+
+const (
+ GuestAccessCanJoin GuestAccess = "can_join"
+ GuestAccessForbidden GuestAccess = "forbidden"
+)
+
+// GuestAccessEventContent represents the content of a m.room.guest_access state event.
+// https://spec.matrix.org/v1.2/client-server-api/#mroomguest_access
+type GuestAccessEventContent struct {
+ GuestAccess GuestAccess `json:"guest_access"`
+}
+
+type BridgeInfoSection struct {
+ ID string `json:"id"`
+ DisplayName string `json:"displayname,omitempty"`
+ AvatarURL id.ContentURIString `json:"avatar_url,omitempty"`
+ ExternalURL string `json:"external_url,omitempty"`
+}
+
+// BridgeEventContent represents the content of a m.bridge state event.
+// https://github.com/matrix-org/matrix-doc/pull/2346
+type BridgeEventContent struct {
+ BridgeBot id.UserID `json:"bridgebot"`
+ Creator id.UserID `json:"creator,omitempty"`
+ Protocol BridgeInfoSection `json:"protocol"`
+ Network *BridgeInfoSection `json:"network,omitempty"`
+ Channel BridgeInfoSection `json:"channel"`
+}
+
+type SpaceChildEventContent struct {
+ Via []string `json:"via,omitempty"`
+ Order string `json:"order,omitempty"`
+ Suggested bool `json:"suggested,omitempty"`
+}
+
+type SpaceParentEventContent struct {
+ Via []string `json:"via,omitempty"`
+ Canonical bool `json:"canonical,omitempty"`
+}
+
+// ModPolicyContent represents the content of a m.room.rule.user, m.room.rule.room, and m.room.rule.server state event.
+// https://spec.matrix.org/v1.2/client-server-api/#moderation-policy-lists
+type ModPolicyContent struct {
+ Entity string `json:"entity"`
+ Reason string `json:"reason"`
+ Recommendation string `json:"recommendation"`
+}
+
+type InsertionMarkerContent struct {
+ InsertionID id.EventID `json:"org.matrix.msc2716.marker.insertion"`
+ Timestamp int64 `json:"com.beeper.timestamp,omitempty"`
+}
diff --git a/vendor/maunium.net/go/mautrix/event/type.go b/vendor/maunium.net/go/mautrix/event/type.go
new file mode 100644
index 00000000..050b17e9
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/event/type.go
@@ -0,0 +1,256 @@
+// Copyright (c) 2021 Tulir Asokan
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package event
+
+import (
+ "encoding/json"
+ "fmt"
+ "strings"
+)
+
+type RoomType string
+
+const (
+ RoomTypeDefault RoomType = ""
+ RoomTypeSpace RoomType = "m.space"
+)
+
+type TypeClass int
+
+func (tc TypeClass) Name() string {
+ switch tc {
+ case MessageEventType:
+ return "message"
+ case StateEventType:
+ return "state"
+ case EphemeralEventType:
+ return "ephemeral"
+ case AccountDataEventType:
+ return "account data"
+ case ToDeviceEventType:
+ return "to-device"
+ default:
+ return "unknown"
+ }
+}
+
+const (
+ // Unknown events
+ UnknownEventType TypeClass = iota
+ // Normal message events
+ MessageEventType
+ // State events
+ StateEventType
+ // Ephemeral events
+ EphemeralEventType
+ // Account data events
+ AccountDataEventType
+ // Device-to-device events
+ ToDeviceEventType
+)
+
+type Type struct {
+ Type string
+ Class TypeClass
+}
+
+func NewEventType(name string) Type {
+ evtType := Type{Type: name}
+ evtType.Class = evtType.GuessClass()
+ return evtType
+}
+
+func (et *Type) IsState() bool {
+ return et.Class == StateEventType
+}
+
+func (et *Type) IsEphemeral() bool {
+ return et.Class == EphemeralEventType
+}
+
+func (et *Type) IsAccountData() bool {
+ return et.Class == AccountDataEventType
+}
+
+func (et *Type) IsToDevice() bool {
+ return et.Class == ToDeviceEventType
+}
+
+func (et *Type) IsInRoomVerification() bool {
+ switch et.Type {
+ case InRoomVerificationStart.Type, InRoomVerificationReady.Type, InRoomVerificationAccept.Type,
+ InRoomVerificationKey.Type, InRoomVerificationMAC.Type, InRoomVerificationCancel.Type:
+ return true
+ default:
+ return false
+ }
+}
+
+func (et *Type) IsCall() bool {
+ switch et.Type {
+ case CallInvite.Type, CallCandidates.Type, CallAnswer.Type, CallReject.Type, CallSelectAnswer.Type,
+ CallNegotiate.Type, CallHangup.Type:
+ return true
+ default:
+ return false
+ }
+}
+
+func (et *Type) IsCustom() bool {
+ return !strings.HasPrefix(et.Type, "m.")
+}
+
+func (et *Type) GuessClass() TypeClass {
+ switch et.Type {
+ case StateAliases.Type, StateCanonicalAlias.Type, StateCreate.Type, StateJoinRules.Type, StateMember.Type,
+ StatePowerLevels.Type, StateRoomName.Type, StateRoomAvatar.Type, StateServerACL.Type, StateTopic.Type,
+ StatePinnedEvents.Type, StateTombstone.Type, StateEncryption.Type, StateBridge.Type, StateHalfShotBridge.Type,
+ StateSpaceParent.Type, StateSpaceChild.Type, StatePolicyRoom.Type, StatePolicyServer.Type, StatePolicyUser.Type,
+ StateInsertionMarker.Type:
+ return StateEventType
+ case EphemeralEventReceipt.Type, EphemeralEventTyping.Type, EphemeralEventPresence.Type:
+ return EphemeralEventType
+ case AccountDataDirectChats.Type, AccountDataPushRules.Type, AccountDataRoomTags.Type,
+ AccountDataSecretStorageKey.Type, AccountDataSecretStorageDefaultKey.Type,
+ AccountDataCrossSigningMaster.Type, AccountDataCrossSigningSelf.Type, AccountDataCrossSigningUser.Type:
+ return AccountDataEventType
+ case EventRedaction.Type, EventMessage.Type, EventEncrypted.Type, EventReaction.Type, EventSticker.Type,
+ InRoomVerificationStart.Type, InRoomVerificationReady.Type, InRoomVerificationAccept.Type,
+ InRoomVerificationKey.Type, InRoomVerificationMAC.Type, InRoomVerificationCancel.Type,
+ CallInvite.Type, CallCandidates.Type, CallAnswer.Type, CallReject.Type, CallSelectAnswer.Type,
+ CallNegotiate.Type, CallHangup.Type, BeeperMessageStatus.Type:
+ return MessageEventType
+ case ToDeviceRoomKey.Type, ToDeviceRoomKeyRequest.Type, ToDeviceForwardedRoomKey.Type, ToDeviceRoomKeyWithheld.Type:
+ return ToDeviceEventType
+ default:
+ return UnknownEventType
+ }
+}
+
+func (et *Type) UnmarshalJSON(data []byte) error {
+ err := json.Unmarshal(data, &et.Type)
+ if err != nil {
+ return err
+ }
+ et.Class = et.GuessClass()
+ return nil
+}
+
+func (et *Type) MarshalJSON() ([]byte, error) {
+ return json.Marshal(&et.Type)
+}
+
+func (et Type) UnmarshalText(data []byte) error {
+ et.Type = string(data)
+ et.Class = et.GuessClass()
+ return nil
+}
+
+func (et Type) MarshalText() ([]byte, error) {
+ return []byte(et.Type), nil
+}
+
+func (et *Type) String() string {
+ return et.Type
+}
+
+func (et *Type) Repr() string {
+ return fmt.Sprintf("%s (%s)", et.Type, et.Class.Name())
+}
+
+// State events
+var (
+ StateAliases = Type{"m.room.aliases", StateEventType}
+ StateCanonicalAlias = Type{"m.room.canonical_alias", StateEventType}
+ StateCreate = Type{"m.room.create", StateEventType}
+ StateJoinRules = Type{"m.room.join_rules", StateEventType}
+ StateHistoryVisibility = Type{"m.room.history_visibility", StateEventType}
+ StateGuestAccess = Type{"m.room.guest_access", StateEventType}
+ StateMember = Type{"m.room.member", StateEventType}
+ StatePowerLevels = Type{"m.room.power_levels", StateEventType}
+ StateRoomName = Type{"m.room.name", StateEventType}
+ StateTopic = Type{"m.room.topic", StateEventType}
+ StateRoomAvatar = Type{"m.room.avatar", StateEventType}
+ StatePinnedEvents = Type{"m.room.pinned_events", StateEventType}
+ StateServerACL = Type{"m.room.server_acl", StateEventType}
+ StateTombstone = Type{"m.room.tombstone", StateEventType}
+ StatePolicyRoom = Type{"m.policy.rule.room", StateEventType}
+ StatePolicyServer = Type{"m.policy.rule.server", StateEventType}
+ StatePolicyUser = Type{"m.policy.rule.user", StateEventType}
+ StateEncryption = Type{"m.room.encryption", StateEventType}
+ StateBridge = Type{"m.bridge", StateEventType}
+ StateHalfShotBridge = Type{"uk.half-shot.bridge", StateEventType}
+ StateSpaceChild = Type{"m.space.child", StateEventType}
+ StateSpaceParent = Type{"m.space.parent", StateEventType}
+ StateInsertionMarker = Type{"org.matrix.msc2716.marker", StateEventType}
+)
+
+// Message events
+var (
+ EventRedaction = Type{"m.room.redaction", MessageEventType}
+ EventMessage = Type{"m.room.message", MessageEventType}
+ EventEncrypted = Type{"m.room.encrypted", MessageEventType}
+ EventReaction = Type{"m.reaction", MessageEventType}
+ EventSticker = Type{"m.sticker", MessageEventType}
+
+ InRoomVerificationStart = Type{"m.key.verification.start", MessageEventType}
+ InRoomVerificationReady = Type{"m.key.verification.ready", MessageEventType}
+ InRoomVerificationAccept = Type{"m.key.verification.accept", MessageEventType}
+ InRoomVerificationKey = Type{"m.key.verification.key", MessageEventType}
+ InRoomVerificationMAC = Type{"m.key.verification.mac", MessageEventType}
+ InRoomVerificationCancel = Type{"m.key.verification.cancel", MessageEventType}
+
+ CallInvite = Type{"m.call.invite", MessageEventType}
+ CallCandidates = Type{"m.call.candidates", MessageEventType}
+ CallAnswer = Type{"m.call.answer", MessageEventType}
+ CallReject = Type{"m.call.reject", MessageEventType}
+ CallSelectAnswer = Type{"m.call.select_answer", MessageEventType}
+ CallNegotiate = Type{"m.call.negotiate", MessageEventType}
+ CallHangup = Type{"m.call.hangup", MessageEventType}
+
+ BeeperMessageStatus = Type{"com.beeper.message_send_status", MessageEventType}
+)
+
+// Ephemeral events
+var (
+ EphemeralEventReceipt = Type{"m.receipt", EphemeralEventType}
+ EphemeralEventTyping = Type{"m.typing", EphemeralEventType}
+ EphemeralEventPresence = Type{"m.presence", EphemeralEventType}
+)
+
+// Account data events
+var (
+ AccountDataDirectChats = Type{"m.direct", AccountDataEventType}
+ AccountDataPushRules = Type{"m.push_rules", AccountDataEventType}
+ AccountDataRoomTags = Type{"m.tag", AccountDataEventType}
+ AccountDataFullyRead = Type{"m.fully_read", AccountDataEventType}
+ AccountDataIgnoredUserList = Type{"m.ignored_user_list", AccountDataEventType}
+
+ AccountDataSecretStorageDefaultKey = Type{"m.secret_storage.default_key", AccountDataEventType}
+ AccountDataSecretStorageKey = Type{"m.secret_storage.key", AccountDataEventType}
+ AccountDataCrossSigningMaster = Type{"m.cross_signing.master", AccountDataEventType}
+ AccountDataCrossSigningUser = Type{"m.cross_signing.user_signing", AccountDataEventType}
+ AccountDataCrossSigningSelf = Type{"m.cross_signing.self_signing", AccountDataEventType}
+)
+
+// Device-to-device events
+var (
+ ToDeviceRoomKey = Type{"m.room_key", ToDeviceEventType}
+ ToDeviceRoomKeyRequest = Type{"m.room_key_request", ToDeviceEventType}
+ ToDeviceForwardedRoomKey = Type{"m.forwarded_room_key", ToDeviceEventType}
+ ToDeviceEncrypted = Type{"m.room.encrypted", ToDeviceEventType}
+ ToDeviceRoomKeyWithheld = Type{"m.room_key.withheld", ToDeviceEventType}
+ ToDeviceDummy = Type{"m.dummy", ToDeviceEventType}
+ ToDeviceVerificationRequest = Type{"m.key.verification.request", ToDeviceEventType}
+ ToDeviceVerificationStart = Type{"m.key.verification.start", ToDeviceEventType}
+ ToDeviceVerificationAccept = Type{"m.key.verification.accept", ToDeviceEventType}
+ ToDeviceVerificationKey = Type{"m.key.verification.key", ToDeviceEventType}
+ ToDeviceVerificationMAC = Type{"m.key.verification.mac", ToDeviceEventType}
+ ToDeviceVerificationCancel = Type{"m.key.verification.cancel", ToDeviceEventType}
+
+ ToDeviceOrgMatrixRoomKeyWithheld = Type{"org.matrix.room_key.withheld", ToDeviceEventType}
+)
diff --git a/vendor/maunium.net/go/mautrix/event/verification.go b/vendor/maunium.net/go/mautrix/event/verification.go
new file mode 100644
index 00000000..8410904d
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/event/verification.go
@@ -0,0 +1,307 @@
+// Copyright (c) 2020 Nikos Filippakis
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package event
+
+import (
+ "maunium.net/go/mautrix/id"
+)
+
+type VerificationMethod string
+
+const VerificationMethodSAS VerificationMethod = "m.sas.v1"
+
+// VerificationRequestEventContent represents the content of a m.key.verification.request to_device event.
+// https://spec.matrix.org/v1.2/client-server-api/#mkeyverificationrequest
+type VerificationRequestEventContent struct {
+ // The device ID which is initiating the request.
+ FromDevice id.DeviceID `json:"from_device"`
+ // An opaque identifier for the verification request. Must be unique with respect to the devices involved.
+ TransactionID string `json:"transaction_id,omitempty"`
+ // The verification methods supported by the sender.
+ Methods []VerificationMethod `json:"methods"`
+ // The POSIX timestamp in milliseconds for when the request was made.
+ Timestamp int64 `json:"timestamp,omitempty"`
+ // The user that the event is sent to for in-room verification.
+ To id.UserID `json:"to,omitempty"`
+ // Original event ID for in-room verification.
+ RelatesTo *RelatesTo `json:"m.relates_to,omitempty"`
+}
+
+func (vrec *VerificationRequestEventContent) SupportsVerificationMethod(meth VerificationMethod) bool {
+ for _, supportedMeth := range vrec.Methods {
+ if supportedMeth == meth {
+ return true
+ }
+ }
+ return false
+}
+
+type KeyAgreementProtocol string
+
+const (
+ KeyAgreementCurve25519 KeyAgreementProtocol = "curve25519"
+ KeyAgreementCurve25519HKDFSHA256 KeyAgreementProtocol = "curve25519-hkdf-sha256"
+)
+
+type VerificationHashMethod string
+
+const VerificationHashSHA256 VerificationHashMethod = "sha256"
+
+type MACMethod string
+
+const HKDFHMACSHA256 MACMethod = "hkdf-hmac-sha256"
+
+type SASMethod string
+
+const (
+ SASDecimal SASMethod = "decimal"
+ SASEmoji SASMethod = "emoji"
+)
+
+// VerificationStartEventContent represents the content of a m.key.verification.start to_device event.
+// https://spec.matrix.org/v1.2/client-server-api/#mkeyverificationstartmsasv1
+type VerificationStartEventContent struct {
+ // The device ID which is initiating the process.
+ FromDevice id.DeviceID `json:"from_device"`
+ // An opaque identifier for the verification process. Must be unique with respect to the devices involved.
+ TransactionID string `json:"transaction_id,omitempty"`
+ // The verification method to use.
+ Method VerificationMethod `json:"method"`
+ // The key agreement protocols the sending device understands.
+ KeyAgreementProtocols []KeyAgreementProtocol `json:"key_agreement_protocols"`
+ // The hash methods the sending device understands.
+ Hashes []VerificationHashMethod `json:"hashes"`
+ // The message authentication codes that the sending device understands.
+ MessageAuthenticationCodes []MACMethod `json:"message_authentication_codes"`
+ // The SAS methods the sending device (and the sending device's user) understands.
+ ShortAuthenticationString []SASMethod `json:"short_authentication_string"`
+ // The user that the event is sent to for in-room verification.
+ To id.UserID `json:"to,omitempty"`
+ // Original event ID for in-room verification.
+ RelatesTo *RelatesTo `json:"m.relates_to,omitempty"`
+}
+
+func (vsec *VerificationStartEventContent) SupportsKeyAgreementProtocol(proto KeyAgreementProtocol) bool {
+ for _, supportedProto := range vsec.KeyAgreementProtocols {
+ if supportedProto == proto {
+ return true
+ }
+ }
+ return false
+}
+
+func (vsec *VerificationStartEventContent) SupportsHashMethod(alg VerificationHashMethod) bool {
+ for _, supportedAlg := range vsec.Hashes {
+ if supportedAlg == alg {
+ return true
+ }
+ }
+ return false
+}
+
+func (vsec *VerificationStartEventContent) SupportsMACMethod(meth MACMethod) bool {
+ for _, supportedMeth := range vsec.MessageAuthenticationCodes {
+ if supportedMeth == meth {
+ return true
+ }
+ }
+ return false
+}
+
+func (vsec *VerificationStartEventContent) SupportsSASMethod(meth SASMethod) bool {
+ for _, supportedMeth := range vsec.ShortAuthenticationString {
+ if supportedMeth == meth {
+ return true
+ }
+ }
+ return false
+}
+
+func (vsec *VerificationStartEventContent) GetRelatesTo() *RelatesTo {
+ if vsec.RelatesTo == nil {
+ vsec.RelatesTo = &RelatesTo{}
+ }
+ return vsec.RelatesTo
+}
+
+func (vsec *VerificationStartEventContent) OptionalGetRelatesTo() *RelatesTo {
+ return vsec.RelatesTo
+}
+
+func (vsec *VerificationStartEventContent) SetRelatesTo(rel *RelatesTo) {
+ vsec.RelatesTo = rel
+}
+
+// VerificationReadyEventContent represents the content of a m.key.verification.ready event.
+// https://spec.matrix.org/v1.2/client-server-api/#mkeyverificationready
+type VerificationReadyEventContent struct {
+ // The device ID which accepted the process.
+ FromDevice id.DeviceID `json:"from_device"`
+ // The verification methods supported by the sender.
+ Methods []VerificationMethod `json:"methods"`
+ // Original event ID for in-room verification.
+ RelatesTo *RelatesTo `json:"m.relates_to,omitempty"`
+}
+
+var _ Relatable = (*VerificationReadyEventContent)(nil)
+
+func (vrec *VerificationReadyEventContent) GetRelatesTo() *RelatesTo {
+ if vrec.RelatesTo == nil {
+ vrec.RelatesTo = &RelatesTo{}
+ }
+ return vrec.RelatesTo
+}
+
+func (vrec *VerificationReadyEventContent) OptionalGetRelatesTo() *RelatesTo {
+ return vrec.RelatesTo
+}
+
+func (vrec *VerificationReadyEventContent) SetRelatesTo(rel *RelatesTo) {
+ vrec.RelatesTo = rel
+}
+
+// VerificationAcceptEventContent represents the content of a m.key.verification.accept to_device event.
+// https://spec.matrix.org/v1.2/client-server-api/#mkeyverificationaccept
+type VerificationAcceptEventContent struct {
+ // An opaque identifier for the verification process. Must be the same as the one used for the m.key.verification.start message.
+ TransactionID string `json:"transaction_id,omitempty"`
+ // The verification method to use.
+ Method VerificationMethod `json:"method"`
+ // The key agreement protocol the device is choosing to use, out of the options in the m.key.verification.start message.
+ KeyAgreementProtocol KeyAgreementProtocol `json:"key_agreement_protocol"`
+ // The hash method the device is choosing to use, out of the options in the m.key.verification.start message.
+ Hash VerificationHashMethod `json:"hash"`
+ // The message authentication code the device is choosing to use, out of the options in the m.key.verification.start message.
+ MessageAuthenticationCode MACMethod `json:"message_authentication_code"`
+ // The SAS methods both devices involved in the verification process understand. Must be a subset of the options in the m.key.verification.start message.
+ ShortAuthenticationString []SASMethod `json:"short_authentication_string"`
+ // The hash (encoded as unpadded base64) of the concatenation of the device's ephemeral public key (encoded as unpadded base64) and the canonical JSON representation of the m.key.verification.start message.
+ Commitment string `json:"commitment"`
+ // The user that the event is sent to for in-room verification.
+ To id.UserID `json:"to,omitempty"`
+ // Original event ID for in-room verification.
+ RelatesTo *RelatesTo `json:"m.relates_to,omitempty"`
+}
+
+func (vaec *VerificationAcceptEventContent) GetRelatesTo() *RelatesTo {
+ if vaec.RelatesTo == nil {
+ vaec.RelatesTo = &RelatesTo{}
+ }
+ return vaec.RelatesTo
+}
+
+func (vaec *VerificationAcceptEventContent) OptionalGetRelatesTo() *RelatesTo {
+ return vaec.RelatesTo
+}
+
+func (vaec *VerificationAcceptEventContent) SetRelatesTo(rel *RelatesTo) {
+ vaec.RelatesTo = rel
+}
+
+// VerificationKeyEventContent represents the content of a m.key.verification.key to_device event.
+// https://spec.matrix.org/v1.2/client-server-api/#mkeyverificationkey
+type VerificationKeyEventContent struct {
+ // An opaque identifier for the verification process. Must be the same as the one used for the m.key.verification.start message.
+ TransactionID string `json:"transaction_id,omitempty"`
+ // The device's ephemeral public key, encoded as unpadded base64.
+ Key string `json:"key"`
+ // The user that the event is sent to for in-room verification.
+ To id.UserID `json:"to,omitempty"`
+ // Original event ID for in-room verification.
+ RelatesTo *RelatesTo `json:"m.relates_to,omitempty"`
+}
+
+func (vkec *VerificationKeyEventContent) GetRelatesTo() *RelatesTo {
+ if vkec.RelatesTo == nil {
+ vkec.RelatesTo = &RelatesTo{}
+ }
+ return vkec.RelatesTo
+}
+
+func (vkec *VerificationKeyEventContent) OptionalGetRelatesTo() *RelatesTo {
+ return vkec.RelatesTo
+}
+
+func (vkec *VerificationKeyEventContent) SetRelatesTo(rel *RelatesTo) {
+ vkec.RelatesTo = rel
+}
+
+// VerificationMacEventContent represents the content of a m.key.verification.mac to_device event.
+// https://spec.matrix.org/v1.2/client-server-api/#mkeyverificationmac
+type VerificationMacEventContent struct {
+ // An opaque identifier for the verification process. Must be the same as the one used for the m.key.verification.start message.
+ TransactionID string `json:"transaction_id,omitempty"`
+ // A map of the key ID to the MAC of the key, using the algorithm in the verification process. The MAC is encoded as unpadded base64.
+ Mac map[id.KeyID]string `json:"mac"`
+ // The MAC of the comma-separated, sorted, list of key IDs given in the mac property, encoded as unpadded base64.
+ Keys string `json:"keys"`
+ // The user that the event is sent to for in-room verification.
+ To id.UserID `json:"to,omitempty"`
+ // Original event ID for in-room verification.
+ RelatesTo *RelatesTo `json:"m.relates_to,omitempty"`
+}
+
+func (vmec *VerificationMacEventContent) GetRelatesTo() *RelatesTo {
+ if vmec.RelatesTo == nil {
+ vmec.RelatesTo = &RelatesTo{}
+ }
+ return vmec.RelatesTo
+}
+
+func (vmec *VerificationMacEventContent) OptionalGetRelatesTo() *RelatesTo {
+ return vmec.RelatesTo
+}
+
+func (vmec *VerificationMacEventContent) SetRelatesTo(rel *RelatesTo) {
+ vmec.RelatesTo = rel
+}
+
+type VerificationCancelCode string
+
+const (
+ VerificationCancelByUser VerificationCancelCode = "m.user"
+ VerificationCancelByTimeout VerificationCancelCode = "m.timeout"
+ VerificationCancelUnknownTransaction VerificationCancelCode = "m.unknown_transaction"
+ VerificationCancelUnknownMethod VerificationCancelCode = "m.unknown_method"
+ VerificationCancelUnexpectedMessage VerificationCancelCode = "m.unexpected_message"
+ VerificationCancelKeyMismatch VerificationCancelCode = "m.key_mismatch"
+ VerificationCancelUserMismatch VerificationCancelCode = "m.user_mismatch"
+ VerificationCancelInvalidMessage VerificationCancelCode = "m.invalid_message"
+ VerificationCancelAccepted VerificationCancelCode = "m.accepted"
+ VerificationCancelSASMismatch VerificationCancelCode = "m.mismatched_sas"
+ VerificationCancelCommitmentMismatch VerificationCancelCode = "m.mismatched_commitment"
+)
+
+// VerificationCancelEventContent represents the content of a m.key.verification.cancel to_device event.
+// https://spec.matrix.org/v1.2/client-server-api/#mkeyverificationcancel
+type VerificationCancelEventContent struct {
+ // The opaque identifier for the verification process/request.
+ TransactionID string `json:"transaction_id,omitempty"`
+ // A human readable description of the code. The client should only rely on this string if it does not understand the code.
+ Reason string `json:"reason"`
+ // The error code for why the process/request was cancelled by the user.
+ Code VerificationCancelCode `json:"code"`
+ // The user that the event is sent to for in-room verification.
+ To id.UserID `json:"to,omitempty"`
+ // Original event ID for in-room verification.
+ RelatesTo *RelatesTo `json:"m.relates_to,omitempty"`
+}
+
+func (vcec *VerificationCancelEventContent) GetRelatesTo() *RelatesTo {
+ if vcec.RelatesTo == nil {
+ vcec.RelatesTo = &RelatesTo{}
+ }
+ return vcec.RelatesTo
+}
+
+func (vcec *VerificationCancelEventContent) OptionalGetRelatesTo() *RelatesTo {
+ return vcec.RelatesTo
+}
+
+func (vcec *VerificationCancelEventContent) SetRelatesTo(rel *RelatesTo) {
+ vcec.RelatesTo = rel
+}
diff --git a/vendor/maunium.net/go/mautrix/event/voip.go b/vendor/maunium.net/go/mautrix/event/voip.go
new file mode 100644
index 00000000..28f56c95
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/event/voip.go
@@ -0,0 +1,116 @@
+// Copyright (c) 2021 Tulir Asokan
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package event
+
+import (
+ "encoding/json"
+ "fmt"
+ "strconv"
+)
+
+type CallHangupReason string
+
+const (
+ CallHangupICEFailed CallHangupReason = "ice_failed"
+ CallHangupInviteTimeout CallHangupReason = "invite_timeout"
+ CallHangupUserHangup CallHangupReason = "user_hangup"
+ CallHangupUserMediaFailed CallHangupReason = "user_media_failed"
+ CallHangupUnknownError CallHangupReason = "unknown_error"
+)
+
+type CallDataType string
+
+const (
+ CallDataTypeOffer CallDataType = "offer"
+ CallDataTypeAnswer CallDataType = "answer"
+)
+
+type CallData struct {
+ SDP string `json:"sdp"`
+ Type CallDataType `json:"type"`
+}
+
+type CallCandidate struct {
+ Candidate string `json:"candidate"`
+ SDPMLineIndex int `json:"sdpMLineIndex"`
+ SDPMID string `json:"sdpMid"`
+}
+
+type CallVersion string
+
+func (cv *CallVersion) UnmarshalJSON(raw []byte) error {
+ var numberVersion int
+ err := json.Unmarshal(raw, &numberVersion)
+ if err != nil {
+ var stringVersion string
+ err = json.Unmarshal(raw, &stringVersion)
+ if err != nil {
+ return fmt.Errorf("failed to parse CallVersion: %w", err)
+ }
+ *cv = CallVersion(stringVersion)
+ } else {
+ *cv = CallVersion(strconv.Itoa(numberVersion))
+ }
+ return nil
+}
+
+func (cv *CallVersion) MarshalJSON() ([]byte, error) {
+ for _, char := range *cv {
+ if char < '0' || char > '9' {
+ // The version contains weird characters, return as string.
+ return json.Marshal(string(*cv))
+ }
+ }
+ // The version consists of only ASCII digits, return as an integer.
+ return []byte(*cv), nil
+}
+
+func (cv *CallVersion) Int() (int, error) {
+ return strconv.Atoi(string(*cv))
+}
+
+type BaseCallEventContent struct {
+ CallID string `json:"call_id"`
+ PartyID string `json:"party_id"`
+ Version CallVersion `json:"version"`
+}
+
+type CallInviteEventContent struct {
+ BaseCallEventContent
+ Lifetime int `json:"lifetime"`
+ Offer CallData `json:"offer"`
+}
+
+type CallCandidatesEventContent struct {
+ BaseCallEventContent
+ Candidates []CallCandidate `json:"candidates"`
+}
+
+type CallRejectEventContent struct {
+ BaseCallEventContent
+}
+
+type CallAnswerEventContent struct {
+ BaseCallEventContent
+ Answer CallData `json:"answer"`
+}
+
+type CallSelectAnswerEventContent struct {
+ BaseCallEventContent
+ SelectedPartyID string `json:"selected_party_id"`
+}
+
+type CallNegotiateEventContent struct {
+ BaseCallEventContent
+ Lifetime int `json:"lifetime"`
+ Description CallData `json:"description"`
+}
+
+type CallHangupEventContent struct {
+ BaseCallEventContent
+ Reason CallHangupReason `json:"reason"`
+}
diff --git a/vendor/maunium.net/go/mautrix/filter.go b/vendor/maunium.net/go/mautrix/filter.go
new file mode 100644
index 00000000..fd6de7a0
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/filter.go
@@ -0,0 +1,93 @@
+// Copyright 2017 Jan Christian Grünhage
+
+package mautrix
+
+import (
+ "errors"
+
+ "maunium.net/go/mautrix/event"
+ "maunium.net/go/mautrix/id"
+)
+
+type EventFormat string
+
+const (
+ EventFormatClient EventFormat = "client"
+ EventFormatFederation EventFormat = "federation"
+)
+
+// Filter is used by clients to specify how the server should filter responses to e.g. sync requests
+// Specified by: https://spec.matrix.org/v1.2/client-server-api/#filtering
+type Filter struct {
+ AccountData FilterPart `json:"account_data,omitempty"`
+ EventFields []string `json:"event_fields,omitempty"`
+ EventFormat EventFormat `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 []id.RoomID `json:"not_rooms,omitempty"`
+ Rooms []id.RoomID `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 []id.RoomID `json:"not_rooms,omitempty"`
+ Rooms []id.RoomID `json:"rooms,omitempty"`
+ Limit int `json:"limit,omitempty"`
+ NotSenders []id.UserID `json:"not_senders,omitempty"`
+ NotTypes []event.Type `json:"not_types,omitempty"`
+ Senders []id.UserID `json:"senders,omitempty"`
+ Types []event.Type `json:"types,omitempty"`
+ ContainsURL *bool `json:"contains_url,omitempty"`
+
+ LazyLoadMembers bool `json:"lazy_load_members,omitempty"`
+ IncludeRedundantMembers bool `json:"include_redundant_members,omitempty"`
+}
+
+// Validate checks if the filter contains valid property values
+func (filter *Filter) Validate() error {
+ if filter.EventFormat != EventFormatClient && filter.EventFormat != EventFormatFederation {
+ 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,
+ }
+}
diff --git a/vendor/maunium.net/go/mautrix/id/contenturi.go b/vendor/maunium.net/go/mautrix/id/contenturi.go
new file mode 100644
index 00000000..cfd00c3e
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/id/contenturi.go
@@ -0,0 +1,158 @@
+// Copyright (c) 2020 Tulir Asokan
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package id
+
+import (
+ "bytes"
+ "database/sql/driver"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "strings"
+)
+
+var (
+ InvalidContentURI = errors.New("invalid Matrix content URI")
+ InputNotJSONString = errors.New("input doesn't look like a JSON string")
+)
+
+// ContentURIString is a string that's expected to be a Matrix content URI.
+// It's useful for delaying the parsing of the content URI to move errors from the event content
+// JSON parsing step to a later step where more appropriate errors can be produced.
+type ContentURIString string
+
+func (uriString ContentURIString) Parse() (ContentURI, error) {
+ return ParseContentURI(string(uriString))
+}
+
+func (uriString ContentURIString) ParseOrIgnore() ContentURI {
+ parsed, _ := ParseContentURI(string(uriString))
+ return parsed
+}
+
+// ContentURI represents a Matrix content URI.
+// https://spec.matrix.org/v1.2/client-server-api/#matrix-content-mxc-uris
+type ContentURI struct {
+ Homeserver string
+ FileID string
+}
+
+func MustParseContentURI(uri string) ContentURI {
+ parsed, err := ParseContentURI(uri)
+ if err != nil {
+ panic(err)
+ }
+ return parsed
+}
+
+// ParseContentURI parses a Matrix content URI.
+func ParseContentURI(uri string) (parsed ContentURI, err error) {
+ if len(uri) == 0 {
+ return
+ } else if !strings.HasPrefix(uri, "mxc://") {
+ err = InvalidContentURI
+ } else if index := strings.IndexRune(uri[6:], '/'); index == -1 || index == len(uri)-7 {
+ err = InvalidContentURI
+ } else {
+ parsed.Homeserver = uri[6 : 6+index]
+ parsed.FileID = uri[6+index+1:]
+ }
+ return
+}
+
+var mxcBytes = []byte("mxc://")
+
+func ParseContentURIBytes(uri []byte) (parsed ContentURI, err error) {
+ if len(uri) == 0 {
+ return
+ } else if !bytes.HasPrefix(uri, mxcBytes) {
+ err = InvalidContentURI
+ } else if index := bytes.IndexRune(uri[6:], '/'); index == -1 || index == len(uri)-7 {
+ err = InvalidContentURI
+ } else {
+ parsed.Homeserver = string(uri[6 : 6+index])
+ parsed.FileID = string(uri[6+index+1:])
+ }
+ return
+}
+
+func (uri *ContentURI) UnmarshalJSON(raw []byte) (err error) {
+ if string(raw) == "null" {
+ *uri = ContentURI{}
+ return nil
+ } else if len(raw) < 2 || raw[0] != '"' || raw[len(raw)-1] != '"' {
+ return InputNotJSONString
+ }
+ parsed, err := ParseContentURIBytes(raw[1 : len(raw)-1])
+ if err != nil {
+ return err
+ }
+ *uri = parsed
+ return nil
+}
+
+func (uri *ContentURI) MarshalJSON() ([]byte, error) {
+ if uri == nil || uri.IsEmpty() {
+ return []byte("null"), nil
+ }
+ return json.Marshal(uri.String())
+}
+
+func (uri *ContentURI) UnmarshalText(raw []byte) (err error) {
+ parsed, err := ParseContentURIBytes(raw)
+ if err != nil {
+ return err
+ }
+ *uri = parsed
+ return nil
+}
+
+func (uri ContentURI) MarshalText() ([]byte, error) {
+ return []byte(uri.String()), nil
+}
+
+func (uri *ContentURI) Scan(i interface{}) error {
+ var parsed ContentURI
+ var err error
+ switch value := i.(type) {
+ case nil:
+ // don't do anything, set uri to empty
+ case []byte:
+ parsed, err = ParseContentURIBytes(value)
+ case string:
+ parsed, err = ParseContentURI(value)
+ default:
+ return fmt.Errorf("invalid type %T for ContentURI.Scan", i)
+ }
+ if err != nil {
+ return err
+ }
+ *uri = parsed
+ return nil
+}
+
+func (uri *ContentURI) Value() (driver.Value, error) {
+ if uri == nil {
+ return nil, nil
+ }
+ return uri.String(), nil
+}
+
+func (uri ContentURI) String() string {
+ if uri.IsEmpty() {
+ return ""
+ }
+ return fmt.Sprintf("mxc://%s/%s", uri.Homeserver, uri.FileID)
+}
+
+func (uri ContentURI) CUString() ContentURIString {
+ return ContentURIString(uri.String())
+}
+
+func (uri ContentURI) IsEmpty() bool {
+ return len(uri.Homeserver) == 0 || len(uri.FileID) == 0
+}
diff --git a/vendor/maunium.net/go/mautrix/id/crypto.go b/vendor/maunium.net/go/mautrix/id/crypto.go
new file mode 100644
index 00000000..84fcd67f
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/id/crypto.go
@@ -0,0 +1,149 @@
+// Copyright (c) 2020 Tulir Asokan
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package id
+
+import (
+ "fmt"
+ "strings"
+)
+
+// OlmMsgType is an Olm message type
+type OlmMsgType int
+
+const (
+ OlmMsgTypePreKey OlmMsgType = 0
+ OlmMsgTypeMsg OlmMsgType = 1
+)
+
+// Algorithm is a Matrix message encryption algorithm.
+// https://spec.matrix.org/v1.2/client-server-api/#messaging-algorithm-names
+type Algorithm string
+
+const (
+ AlgorithmOlmV1 Algorithm = "m.olm.v1.curve25519-aes-sha2"
+ AlgorithmMegolmV1 Algorithm = "m.megolm.v1.aes-sha2"
+)
+
+type KeyAlgorithm string
+
+const (
+ KeyAlgorithmCurve25519 KeyAlgorithm = "curve25519"
+ KeyAlgorithmEd25519 KeyAlgorithm = "ed25519"
+ KeyAlgorithmSignedCurve25519 KeyAlgorithm = "signed_curve25519"
+)
+
+type CrossSigningUsage string
+
+const (
+ XSUsageMaster CrossSigningUsage = "master"
+ XSUsageSelfSigning CrossSigningUsage = "self_signing"
+ XSUsageUserSigning CrossSigningUsage = "user_signing"
+)
+
+// A SessionID is an arbitrary string that identifies an Olm or Megolm session.
+type SessionID string
+
+func (sessionID SessionID) String() string {
+ return string(sessionID)
+}
+
+// Ed25519 is the base64 representation of an Ed25519 public key
+type Ed25519 string
+type SigningKey = Ed25519
+
+func (ed25519 Ed25519) String() string {
+ return string(ed25519)
+}
+
+func (ed25519 Ed25519) Fingerprint() string {
+ spacedSigningKey := make([]byte, len(ed25519)+(len(ed25519)-1)/4)
+ var ptr = 0
+ for i, chr := range ed25519 {
+ spacedSigningKey[ptr] = byte(chr)
+ ptr++
+ if i%4 == 3 {
+ spacedSigningKey[ptr] = ' '
+ ptr++
+ }
+ }
+ return string(spacedSigningKey)
+}
+
+// Curve25519 is the base64 representation of an Curve25519 public key
+type Curve25519 string
+type SenderKey = Curve25519
+type IdentityKey = Curve25519
+
+func (curve25519 Curve25519) String() string {
+ return string(curve25519)
+}
+
+// A DeviceID is an arbitrary string that references a specific device.
+type DeviceID string
+
+func (deviceID DeviceID) String() string {
+ return string(deviceID)
+}
+
+// A DeviceKeyID is a string formatted as <algorithm>:<device_id> that is used as the key in deviceid-key mappings.
+type DeviceKeyID string
+
+func NewDeviceKeyID(algorithm KeyAlgorithm, deviceID DeviceID) DeviceKeyID {
+ return DeviceKeyID(fmt.Sprintf("%s:%s", algorithm, deviceID))
+}
+
+func (deviceKeyID DeviceKeyID) String() string {
+ return string(deviceKeyID)
+}
+
+func (deviceKeyID DeviceKeyID) Parse() (Algorithm, DeviceID) {
+ index := strings.IndexRune(string(deviceKeyID), ':')
+ if index < 0 || len(deviceKeyID) <= index+1 {
+ return "", ""
+ }
+ return Algorithm(deviceKeyID[:index]), DeviceID(deviceKeyID[index+1:])
+}
+
+// A KeyID a string formatted as <keyalgorithm>:<key_id> that is used as the key in one-time-key mappings.
+type KeyID string
+
+func NewKeyID(algorithm KeyAlgorithm, keyID string) KeyID {
+ return KeyID(fmt.Sprintf("%s:%s", algorithm, keyID))
+}
+
+func (keyID KeyID) String() string {
+ return string(keyID)
+}
+
+func (keyID KeyID) Parse() (KeyAlgorithm, string) {
+ index := strings.IndexRune(string(keyID), ':')
+ if index < 0 || len(keyID) <= index+1 {
+ return "", ""
+ }
+ return KeyAlgorithm(keyID[:index]), string(keyID[index+1:])
+}
+
+// Device contains the identity details of a device and some additional info.
+type Device struct {
+ UserID UserID
+ DeviceID DeviceID
+ IdentityKey Curve25519
+ SigningKey Ed25519
+
+ Trust TrustState
+ Deleted bool
+ Name string
+}
+
+func (device *Device) Fingerprint() string {
+ return device.SigningKey.Fingerprint()
+}
+
+type CrossSigningKey struct {
+ Key Ed25519
+ First Ed25519
+}
diff --git a/vendor/maunium.net/go/mautrix/id/matrixuri.go b/vendor/maunium.net/go/mautrix/id/matrixuri.go
new file mode 100644
index 00000000..5ec403e9
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/id/matrixuri.go
@@ -0,0 +1,293 @@
+// Copyright (c) 2021 Tulir Asokan
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package id
+
+import (
+ "errors"
+ "fmt"
+ "net/url"
+ "strings"
+)
+
+// Errors that can happen when parsing matrix: URIs
+var (
+ ErrInvalidScheme = errors.New("matrix URI scheme must be exactly 'matrix'")
+ ErrInvalidPartCount = errors.New("matrix URIs must have exactly 2 or 4 segments")
+ ErrInvalidFirstSegment = errors.New("invalid identifier in first segment of matrix URI")
+ ErrEmptySecondSegment = errors.New("the second segment of the matrix URI must not be empty")
+ ErrInvalidThirdSegment = errors.New("invalid identifier in third segment of matrix URI")
+ ErrEmptyFourthSegment = errors.New("the fourth segment of the matrix URI must not be empty when the third segment is present")
+)
+
+// Errors that can happen when parsing matrix.to URLs
+var (
+ ErrNotMatrixTo = errors.New("that URL is not a matrix.to URL")
+ ErrInvalidMatrixToPartCount = errors.New("matrix.to URLs must have exactly 1 or 2 segments")
+ ErrEmptyMatrixToPrimaryIdentifier = errors.New("the primary identifier in the matrix.to URL is empty")
+ ErrInvalidMatrixToPrimaryIdentifier = errors.New("the primary identifier in the matrix.to URL has an invalid sigil")
+ ErrInvalidMatrixToSecondaryIdentifier = errors.New("the secondary identifier in the matrix.to URL has an invalid sigil")
+)
+
+var ErrNotMatrixToOrMatrixURI = errors.New("that URL is not a matrix.to URL nor matrix: URI")
+
+// MatrixURI contains the result of parsing a matrix: URI using ParseMatrixURI
+type MatrixURI struct {
+ Sigil1 rune
+ Sigil2 rune
+ MXID1 string
+ MXID2 string
+ Via []string
+ Action string
+}
+
+// SigilToPathSegment contains a mapping from Matrix identifier sigils to matrix: URI path segments.
+var SigilToPathSegment = map[rune]string{
+ '$': "e",
+ '#': "r",
+ '!': "roomid",
+ '@': "u",
+}
+
+func (uri *MatrixURI) getQuery() url.Values {
+ q := make(url.Values)
+ if uri.Via != nil && len(uri.Via) > 0 {
+ q["via"] = uri.Via
+ }
+ if len(uri.Action) > 0 {
+ q.Set("action", uri.Action)
+ }
+ return q
+}
+
+// String converts the parsed matrix: URI back into the string representation.
+func (uri *MatrixURI) String() string {
+ parts := []string{
+ SigilToPathSegment[uri.Sigil1],
+ url.PathEscape(uri.MXID1),
+ }
+ if uri.Sigil2 != 0 {
+ parts = append(parts, SigilToPathSegment[uri.Sigil2], url.PathEscape(uri.MXID2))
+ }
+ return (&url.URL{
+ Scheme: "matrix",
+ Opaque: strings.Join(parts, "/"),
+ RawQuery: uri.getQuery().Encode(),
+ }).String()
+}
+
+// MatrixToURL converts to parsed matrix: URI into a matrix.to URL
+func (uri *MatrixURI) MatrixToURL() string {
+ fragment := fmt.Sprintf("#/%s", url.PathEscape(uri.PrimaryIdentifier()))
+ if uri.Sigil2 != 0 {
+ fragment = fmt.Sprintf("%s/%s", fragment, url.PathEscape(uri.SecondaryIdentifier()))
+ }
+ query := uri.getQuery().Encode()
+ if len(query) > 0 {
+ fragment = fmt.Sprintf("%s?%s", fragment, query)
+ }
+ // It would be nice to use URL{...}.String() here, but figuring out the Fragment vs RawFragment stuff is a pain
+ return fmt.Sprintf("https://matrix.to/%s", fragment)
+}
+
+// PrimaryIdentifier returns the first Matrix identifier in the URI.
+// Currently room IDs, room aliases and user IDs can be in the primary identifier slot.
+func (uri *MatrixURI) PrimaryIdentifier() string {
+ return fmt.Sprintf("%c%s", uri.Sigil1, uri.MXID1)
+}
+
+// SecondaryIdentifier returns the second Matrix identifier in the URI.
+// Currently only event IDs can be in the secondary identifier slot.
+func (uri *MatrixURI) SecondaryIdentifier() string {
+ if uri.Sigil2 == 0 {
+ return ""
+ }
+ return fmt.Sprintf("%c%s", uri.Sigil2, uri.MXID2)
+}
+
+// UserID returns the user ID from the URI if the primary identifier is a user ID.
+func (uri *MatrixURI) UserID() UserID {
+ if uri.Sigil1 == '@' {
+ return UserID(uri.PrimaryIdentifier())
+ }
+ return ""
+}
+
+// RoomID returns the room ID from the URI if the primary identifier is a room ID.
+func (uri *MatrixURI) RoomID() RoomID {
+ if uri.Sigil1 == '!' {
+ return RoomID(uri.PrimaryIdentifier())
+ }
+ return ""
+}
+
+// RoomAlias returns the room alias from the URI if the primary identifier is a room alias.
+func (uri *MatrixURI) RoomAlias() RoomAlias {
+ if uri.Sigil1 == '#' {
+ return RoomAlias(uri.PrimaryIdentifier())
+ }
+ return ""
+}
+
+// EventID returns the event ID from the URI if the primary identifier is a room ID or alias and the secondary identifier is an event ID.
+func (uri *MatrixURI) EventID() EventID {
+ if (uri.Sigil1 == '!' || uri.Sigil1 == '#') && uri.Sigil2 == '$' {
+ return EventID(uri.SecondaryIdentifier())
+ }
+ return ""
+}
+
+// ParseMatrixURIOrMatrixToURL parses the given matrix.to URL or matrix: URI into a unified representation.
+func ParseMatrixURIOrMatrixToURL(uri string) (*MatrixURI, error) {
+ parsed, err := url.Parse(uri)
+ if err != nil {
+ return nil, fmt.Errorf("failed to parse URI: %w", err)
+ }
+ if parsed.Scheme == "matrix" {
+ return ProcessMatrixURI(parsed)
+ } else if strings.HasSuffix(parsed.Hostname(), "matrix.to") {
+ return ProcessMatrixToURL(parsed)
+ } else {
+ return nil, ErrNotMatrixToOrMatrixURI
+ }
+}
+
+// ParseMatrixURI implements the matrix: URI parsing algorithm.
+//
+// Currently specified in https://github.com/matrix-org/matrix-doc/blob/master/proposals/2312-matrix-uri.md#uri-parsing-algorithm
+func ParseMatrixURI(uri string) (*MatrixURI, error) {
+ // Step 1: parse the URI according to RFC 3986
+ parsed, err := url.Parse(uri)
+ if err != nil {
+ return nil, fmt.Errorf("failed to parse URI: %w", err)
+ }
+ return ProcessMatrixURI(parsed)
+}
+
+// ProcessMatrixURI implements steps 2-7 of the matrix: URI parsing algorithm
+// (i.e. everything except parsing the URI itself, which is done with url.Parse or ParseMatrixURI)
+func ProcessMatrixURI(uri *url.URL) (*MatrixURI, error) {
+ // Step 2: check that scheme is exactly `matrix`
+ if uri.Scheme != "matrix" {
+ return nil, ErrInvalidScheme
+ }
+
+ // Step 3: split the path into segments separated by /
+ parts := strings.Split(uri.Opaque, "/")
+
+ // Step 4: Check that the URI contains either 2 or 4 segments
+ if len(parts) != 2 && len(parts) != 4 {
+ return nil, ErrInvalidPartCount
+ }
+
+ var parsed MatrixURI
+
+ // Step 5: Construct the top-level Matrix identifier
+ // a: find the sigil from the first segment
+ switch parts[0] {
+ case "u", "user":
+ parsed.Sigil1 = '@'
+ case "r", "room":
+ parsed.Sigil1 = '#'
+ case "roomid":
+ parsed.Sigil1 = '!'
+ default:
+ return nil, fmt.Errorf("%w: '%s'", ErrInvalidFirstSegment, parts[0])
+ }
+ // b: find the identifier from the second segment
+ if len(parts[1]) == 0 {
+ return nil, ErrEmptySecondSegment
+ }
+ parsed.MXID1 = parts[1]
+
+ // Step 6: if the first part is a room and the URI has 4 segments, construct a second level identifier
+ if (parsed.Sigil1 == '!' || parsed.Sigil1 == '#') && len(parts) == 4 {
+ // a: find the sigil from the third segment
+ switch parts[2] {
+ case "e", "event":
+ parsed.Sigil2 = '$'
+ default:
+ return nil, fmt.Errorf("%w: '%s'", ErrInvalidThirdSegment, parts[0])
+ }
+
+ // b: find the identifier from the fourth segment
+ if len(parts[3]) == 0 {
+ return nil, ErrEmptyFourthSegment
+ }
+ parsed.MXID2 = parts[3]
+ }
+
+ // Step 7: parse the query and extract via and action items
+ via, ok := uri.Query()["via"]
+ if ok && len(via) > 0 {
+ parsed.Via = via
+ }
+ action, ok := uri.Query()["action"]
+ if ok && len(action) > 0 {
+ parsed.Action = action[len(action)-1]
+ }
+
+ return &parsed, nil
+}
+
+// ParseMatrixToURL parses a matrix.to URL into the same container as ParseMatrixURI parses matrix: URIs.
+func ParseMatrixToURL(uri string) (*MatrixURI, error) {
+ parsed, err := url.Parse(uri)
+ if err != nil {
+ return nil, fmt.Errorf("failed to parse URL: %w", err)
+ }
+ return ProcessMatrixToURL(parsed)
+}
+
+// ProcessMatrixToURL is the equivalent of ProcessMatrixURI for matrix.to URLs.
+func ProcessMatrixToURL(uri *url.URL) (*MatrixURI, error) {
+ if !strings.HasSuffix(uri.Hostname(), "matrix.to") {
+ return nil, ErrNotMatrixTo
+ }
+
+ initialSplit := strings.SplitN(uri.Fragment, "?", 2)
+ parts := strings.Split(initialSplit[0], "/")
+ if len(initialSplit) > 1 {
+ uri.RawQuery = initialSplit[1]
+ }
+
+ if len(parts) < 2 || len(parts) > 3 {
+ return nil, ErrInvalidMatrixToPartCount
+ }
+
+ if len(parts[1]) == 0 {
+ return nil, ErrEmptyMatrixToPrimaryIdentifier
+ }
+
+ var parsed MatrixURI
+
+ parsed.Sigil1 = rune(parts[1][0])
+ parsed.MXID1 = parts[1][1:]
+ _, isKnown := SigilToPathSegment[parsed.Sigil1]
+ if !isKnown {
+ return nil, ErrInvalidMatrixToPrimaryIdentifier
+ }
+
+ if len(parts) == 3 && len(parts[2]) > 0 {
+ parsed.Sigil2 = rune(parts[2][0])
+ parsed.MXID2 = parts[2][1:]
+ _, isKnown = SigilToPathSegment[parsed.Sigil2]
+ if !isKnown {
+ return nil, ErrInvalidMatrixToSecondaryIdentifier
+ }
+ }
+
+ via, ok := uri.Query()["via"]
+ if ok && len(via) > 0 {
+ parsed.Via = via
+ }
+ action, ok := uri.Query()["action"]
+ if ok && len(action) > 0 {
+ parsed.Action = action[len(action)-1]
+ }
+
+ return &parsed, nil
+}
diff --git a/vendor/maunium.net/go/mautrix/id/opaque.go b/vendor/maunium.net/go/mautrix/id/opaque.go
new file mode 100644
index 00000000..16863b95
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/id/opaque.go
@@ -0,0 +1,83 @@
+// Copyright (c) 2020 Tulir Asokan
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package id
+
+import (
+ "fmt"
+)
+
+// A RoomID is a string starting with ! that references a specific room.
+// https://matrix.org/docs/spec/appendices#room-ids-and-event-ids
+type RoomID string
+
+// A RoomAlias is a string starting with # that can be resolved into.
+// https://matrix.org/docs/spec/appendices#room-aliases
+type RoomAlias string
+
+func NewRoomAlias(localpart, server string) RoomAlias {
+ return RoomAlias(fmt.Sprintf("#%s:%s", localpart, server))
+}
+
+// An EventID is a string starting with $ that references a specific event.
+//
+// https://matrix.org/docs/spec/appendices#room-ids-and-event-ids
+// https://matrix.org/docs/spec/rooms/v4#event-ids
+type EventID string
+
+// A BatchID is a string identifying a batch of events being backfilled to a room.
+// https://github.com/matrix-org/matrix-doc/pull/2716
+type BatchID string
+
+func (roomID RoomID) String() string {
+ return string(roomID)
+}
+
+func (roomID RoomID) URI(via ...string) *MatrixURI {
+ return &MatrixURI{
+ Sigil1: '!',
+ MXID1: string(roomID)[1:],
+ Via: via,
+ }
+}
+
+func (roomID RoomID) EventURI(eventID EventID, via ...string) *MatrixURI {
+ return &MatrixURI{
+ Sigil1: '!',
+ MXID1: string(roomID)[1:],
+ Sigil2: '$',
+ MXID2: string(eventID)[1:],
+ Via: via,
+ }
+}
+
+func (roomAlias RoomAlias) String() string {
+ return string(roomAlias)
+}
+
+func (roomAlias RoomAlias) URI() *MatrixURI {
+ return &MatrixURI{
+ Sigil1: '#',
+ MXID1: string(roomAlias)[1:],
+ }
+}
+
+func (roomAlias RoomAlias) EventURI(eventID EventID) *MatrixURI {
+ return &MatrixURI{
+ Sigil1: '#',
+ MXID1: string(roomAlias)[1:],
+ Sigil2: '$',
+ MXID2: string(eventID)[1:],
+ }
+}
+
+func (eventID EventID) String() string {
+ return string(eventID)
+}
+
+func (batchID BatchID) String() string {
+ return string(batchID)
+}
diff --git a/vendor/maunium.net/go/mautrix/id/trust.go b/vendor/maunium.net/go/mautrix/id/trust.go
new file mode 100644
index 00000000..04f6e36b
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/id/trust.go
@@ -0,0 +1,87 @@
+// Copyright (c) 2022 Tulir Asokan
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package id
+
+import (
+ "fmt"
+ "strings"
+)
+
+// TrustState determines how trusted a device is.
+type TrustState int
+
+const (
+ TrustStateBlacklisted TrustState = -100
+ TrustStateUnset TrustState = 0
+ TrustStateUnknownDevice TrustState = 10
+ TrustStateForwarded TrustState = 20
+ TrustStateCrossSignedUntrusted TrustState = 50
+ TrustStateCrossSignedTOFU TrustState = 100
+ TrustStateCrossSignedVerified TrustState = 200
+ TrustStateVerified TrustState = 300
+ TrustStateInvalid TrustState = (1 << 31) - 1
+)
+
+func (ts *TrustState) UnmarshalText(data []byte) error {
+ strData := string(data)
+ state := ParseTrustState(strData)
+ if state == TrustStateInvalid {
+ return fmt.Errorf("invalid trust state %q", strData)
+ }
+ *ts = state
+ return nil
+}
+
+func (ts *TrustState) MarshalText() ([]byte, error) {
+ return []byte(ts.String()), nil
+}
+
+func ParseTrustState(val string) TrustState {
+ switch strings.ToLower(val) {
+ case "blacklisted":
+ return TrustStateBlacklisted
+ case "unverified":
+ return TrustStateUnset
+ case "cross-signed-untrusted":
+ return TrustStateCrossSignedUntrusted
+ case "unknown-device":
+ return TrustStateUnknownDevice
+ case "forwarded":
+ return TrustStateForwarded
+ case "cross-signed-tofu", "cross-signed":
+ return TrustStateCrossSignedTOFU
+ case "cross-signed-verified", "cross-signed-trusted":
+ return TrustStateCrossSignedVerified
+ case "verified":
+ return TrustStateVerified
+ default:
+ return TrustStateInvalid
+ }
+}
+
+func (ts TrustState) String() string {
+ switch ts {
+ case TrustStateBlacklisted:
+ return "blacklisted"
+ case TrustStateUnset:
+ return "unverified"
+ case TrustStateCrossSignedUntrusted:
+ return "cross-signed-untrusted"
+ case TrustStateUnknownDevice:
+ return "unknown-device"
+ case TrustStateForwarded:
+ return "forwarded"
+ case TrustStateCrossSignedTOFU:
+ return "cross-signed-tofu"
+ case TrustStateCrossSignedVerified:
+ return "cross-signed-verified"
+ case TrustStateVerified:
+ return "verified"
+ default:
+ return "invalid"
+ }
+}
diff --git a/vendor/maunium.net/go/mautrix/id/userid.go b/vendor/maunium.net/go/mautrix/id/userid.go
new file mode 100644
index 00000000..0522b54c
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/id/userid.go
@@ -0,0 +1,224 @@
+// Copyright (c) 2021 Tulir Asokan
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package id
+
+import (
+ "bytes"
+ "encoding/hex"
+ "errors"
+ "fmt"
+ "regexp"
+ "strings"
+)
+
+// UserID represents a Matrix user ID.
+// https://matrix.org/docs/spec/appendices#user-identifiers
+type UserID string
+
+const UserIDMaxLength = 255
+
+func NewUserID(localpart, homeserver string) UserID {
+ return UserID(fmt.Sprintf("@%s:%s", localpart, homeserver))
+}
+
+func NewEncodedUserID(localpart, homeserver string) UserID {
+ return NewUserID(EncodeUserLocalpart(localpart), homeserver)
+}
+
+var (
+ ErrInvalidUserID = errors.New("is not a valid user ID")
+ ErrNoncompliantLocalpart = errors.New("contains characters that are not allowed")
+ ErrUserIDTooLong = errors.New("the given user ID is longer than 255 characters")
+ ErrEmptyLocalpart = errors.New("empty localparts are not allowed")
+)
+
+// Parse parses the user ID into the localpart and server name.
+//
+// Note that this only enforces very basic user ID formatting requirements: user IDs start with
+// a @, and contain a : after the @. If you want to enforce localpart validity, see the
+// ParseAndValidate and ValidateUserLocalpart functions.
+func (userID UserID) Parse() (localpart, homeserver string, err error) {
+ if len(userID) == 0 || userID[0] != '@' || !strings.ContainsRune(string(userID), ':') {
+ // This error wrapping lets you use errors.Is() nicely even though the message contains the user ID
+ err = fmt.Errorf("'%s' %w", userID, ErrInvalidUserID)
+ return
+ }
+ parts := strings.SplitN(string(userID), ":", 2)
+ localpart, homeserver = strings.TrimPrefix(parts[0], "@"), parts[1]
+ return
+}
+
+func (userID UserID) Localpart() string {
+ localpart, _, _ := userID.Parse()
+ return localpart
+}
+
+func (userID UserID) Homeserver() string {
+ _, homeserver, _ := userID.Parse()
+ return homeserver
+}
+
+// URI returns the user ID as a MatrixURI struct, which can then be stringified into a matrix: URI or a matrix.to URL.
+//
+// This does not parse or validate the user ID. Use the ParseAndValidate method if you want to ensure the user ID is valid first.
+func (userID UserID) URI() *MatrixURI {
+ return &MatrixURI{
+ Sigil1: '@',
+ MXID1: string(userID)[1:],
+ }
+}
+
+var ValidLocalpartRegex = regexp.MustCompile("^[0-9a-z-.=_/]+$")
+
+// ValidateUserLocalpart validates a Matrix user ID localpart using the grammar
+// in https://matrix.org/docs/spec/appendices#user-identifier
+func ValidateUserLocalpart(localpart string) error {
+ if len(localpart) == 0 {
+ return ErrEmptyLocalpart
+ } else if !ValidLocalpartRegex.MatchString(localpart) {
+ return fmt.Errorf("'%s' %w", localpart, ErrNoncompliantLocalpart)
+ }
+ return nil
+}
+
+// ParseAndValidate parses the user ID into the localpart and server name like Parse,
+// and also validates that the localpart is allowed according to the user identifiers spec.
+func (userID UserID) ParseAndValidate() (localpart, homeserver string, err error) {
+ localpart, homeserver, err = userID.Parse()
+ if err == nil {
+ err = ValidateUserLocalpart(localpart)
+ }
+ if err == nil && len(userID) > UserIDMaxLength {
+ err = ErrUserIDTooLong
+ }
+ return
+}
+
+func (userID UserID) ParseAndDecode() (localpart, homeserver string, err error) {
+ localpart, homeserver, err = userID.ParseAndValidate()
+ if err == nil {
+ localpart, err = DecodeUserLocalpart(localpart)
+ }
+ return
+}
+
+func (userID UserID) String() string {
+ return string(userID)
+}
+
+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 https://spec.matrix.org/v1.2/appendices/#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 https://spec.matrix.org/v1.2/appendices/#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
+}
diff --git a/vendor/maunium.net/go/mautrix/pushrules/action.go b/vendor/maunium.net/go/mautrix/pushrules/action.go
new file mode 100644
index 00000000..844c3eec
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/pushrules/action.go
@@ -0,0 +1,124 @@
+// Copyright (c) 2020 Tulir Asokan
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package pushrules
+
+import "encoding/json"
+
+// PushActionType is the type of a PushAction
+type PushActionType string
+
+// The allowed push action types as specified in spec section 11.12.1.4.1.
+const (
+ ActionNotify PushActionType = "notify"
+ ActionDontNotify PushActionType = "dont_notify"
+ ActionCoalesce PushActionType = "coalesce"
+ ActionSetTweak PushActionType = "set_tweak"
+)
+
+// PushActionTweak is the type of the tweak in SetTweak push actions.
+type PushActionTweak string
+
+// The allowed tweak types as specified in spec section 11.12.1.4.1.1.
+const (
+ TweakSound PushActionTweak = "sound"
+ TweakHighlight PushActionTweak = "highlight"
+)
+
+// PushActionArray is an array of PushActions.
+type PushActionArray []*PushAction
+
+// PushActionArrayShould contains the important information parsed from a PushActionArray.
+type PushActionArrayShould struct {
+ // Whether or not the array contained a Notify, DontNotify or Coalesce action type.
+ NotifySpecified bool
+ // Whether or not the event in question should trigger a notification.
+ Notify bool
+ // Whether or not the event in question should be highlighted.
+ Highlight bool
+
+ // Whether or not the event in question should trigger a sound alert.
+ PlaySound bool
+ // The name of the sound to play if PlaySound is true.
+ SoundName string
+}
+
+// Should parses this push action array and returns the relevant details wrapped in a PushActionArrayShould struct.
+func (actions PushActionArray) Should() (should PushActionArrayShould) {
+ for _, action := range actions {
+ switch action.Action {
+ case ActionNotify, ActionCoalesce:
+ should.Notify = true
+ should.NotifySpecified = true
+ case ActionDontNotify:
+ should.Notify = false
+ should.NotifySpecified = true
+ case ActionSetTweak:
+ switch action.Tweak {
+ case TweakHighlight:
+ var ok bool
+ should.Highlight, ok = action.Value.(bool)
+ if !ok {
+ // Highlight value not specified, so assume true since the tweak is set.
+ should.Highlight = true
+ }
+ case TweakSound:
+ should.SoundName = action.Value.(string)
+ should.PlaySound = len(should.SoundName) > 0
+ }
+ }
+ }
+ return
+}
+
+// PushAction is a single action that should be triggered when receiving a message.
+type PushAction struct {
+ Action PushActionType
+ Tweak PushActionTweak
+ Value interface{}
+}
+
+// UnmarshalJSON parses JSON into this PushAction.
+//
+// - If the JSON is a single string, the value is stored in the Action field.
+// - If the JSON is an object with the set_tweak field, Action will be set to
+// "set_tweak", Tweak will be set to the value of the set_tweak field and
+// and Value will be set to the value of the value field.
+// - In any other case, the function does nothing.
+func (action *PushAction) UnmarshalJSON(raw []byte) error {
+ var data interface{}
+
+ err := json.Unmarshal(raw, &data)
+ if err != nil {
+ return err
+ }
+
+ switch val := data.(type) {
+ case string:
+ action.Action = PushActionType(val)
+ case map[string]interface{}:
+ tweak, ok := val["set_tweak"].(string)
+ if ok {
+ action.Action = ActionSetTweak
+ action.Tweak = PushActionTweak(tweak)
+ action.Value, _ = val["value"]
+ }
+ }
+ return nil
+}
+
+// MarshalJSON is the reverse of UnmarshalJSON()
+func (action *PushAction) MarshalJSON() (raw []byte, err error) {
+ if action.Action == ActionSetTweak {
+ data := map[string]interface{}{
+ "set_tweak": action.Tweak,
+ "value": action.Value,
+ }
+ return json.Marshal(&data)
+ }
+ data := string(action.Action)
+ return json.Marshal(&data)
+}
diff --git a/vendor/maunium.net/go/mautrix/pushrules/condition.go b/vendor/maunium.net/go/mautrix/pushrules/condition.go
new file mode 100644
index 00000000..f809f8e7
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/pushrules/condition.go
@@ -0,0 +1,266 @@
+// Copyright (c) 2022 Tulir Asokan
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package pushrules
+
+import (
+ "encoding/json"
+ "fmt"
+ "regexp"
+ "strconv"
+ "strings"
+ "unicode"
+
+ "github.com/tidwall/gjson"
+
+ "maunium.net/go/mautrix/event"
+ "maunium.net/go/mautrix/id"
+ "maunium.net/go/mautrix/pushrules/glob"
+)
+
+// Room is an interface with the functions that are needed for processing room-specific push conditions
+type Room interface {
+ GetOwnDisplayname() string
+ GetMemberCount() int
+}
+
+// EventfulRoom is an extension of Room to support MSC3664.
+type EventfulRoom interface {
+ Room
+ GetEvent(id.EventID) *event.Event
+}
+
+// PushCondKind is the type of a push condition.
+type PushCondKind string
+
+// The allowed push condition kinds as specified in https://spec.matrix.org/v1.2/client-server-api/#conditions-1
+const (
+ KindEventMatch PushCondKind = "event_match"
+ KindContainsDisplayName PushCondKind = "contains_display_name"
+ KindRoomMemberCount PushCondKind = "room_member_count"
+
+ // MSC3664: https://github.com/matrix-org/matrix-spec-proposals/pull/3664
+
+ KindRelatedEventMatch PushCondKind = "related_event_match"
+ KindUnstableRelatedEventMatch PushCondKind = "im.nheko.msc3664.related_event_match"
+)
+
+// PushCondition wraps a condition that is required for a specific PushRule to be used.
+type PushCondition struct {
+ // The type of the condition.
+ Kind PushCondKind `json:"kind"`
+ // The dot-separated field of the event to match. Only applicable if kind is EventMatch.
+ Key string `json:"key,omitempty"`
+ // The glob-style pattern to match the field against. Only applicable if kind is EventMatch.
+ Pattern string `json:"pattern,omitempty"`
+ // The condition that needs to be fulfilled for RoomMemberCount-type conditions.
+ // A decimal integer optionally prefixed by ==, <, >, >= or <=. Prefix "==" is assumed if no prefix found.
+ MemberCountCondition string `json:"is,omitempty"`
+
+ // The relation type for related_event_match from MSC3664
+ RelType event.RelationType `json:"rel_type,omitempty"`
+}
+
+// MemberCountFilterRegex is the regular expression to parse the MemberCountCondition of PushConditions.
+var MemberCountFilterRegex = regexp.MustCompile("^(==|[<>]=?)?([0-9]+)$")
+
+// Match checks if this condition is fulfilled for the given event in the given room.
+func (cond *PushCondition) Match(room Room, evt *event.Event) bool {
+ switch cond.Kind {
+ case KindEventMatch:
+ return cond.matchValue(room, evt)
+ case KindRelatedEventMatch, KindUnstableRelatedEventMatch:
+ return cond.matchRelatedEvent(room, evt)
+ case KindContainsDisplayName:
+ return cond.matchDisplayName(room, evt)
+ case KindRoomMemberCount:
+ return cond.matchMemberCount(room)
+ default:
+ return false
+ }
+}
+
+func splitWithEscaping(s string, separator, escape byte) []string {
+ var token []byte
+ var tokens []string
+ for i := 0; i < len(s); i++ {
+ if s[i] == separator {
+ tokens = append(tokens, string(token))
+ token = token[:0]
+ } else if s[i] == escape && i+1 < len(s) {
+ i++
+ token = append(token, s[i])
+ } else {
+ token = append(token, s[i])
+ }
+ }
+ tokens = append(tokens, string(token))
+ return tokens
+}
+
+func hackyNestedGet(data map[string]interface{}, path []string) (interface{}, bool) {
+ val, ok := data[path[0]]
+ if len(path) == 1 {
+ // We don't have any more path parts, return the value regardless of whether it exists or not.
+ return val, ok
+ } else if ok {
+ if mapVal, ok := val.(map[string]interface{}); ok {
+ val, ok = hackyNestedGet(mapVal, path[1:])
+ if ok {
+ return val, true
+ }
+ }
+ }
+ // If we don't find the key, try to combine the first two parts.
+ // e.g. if the key is content.m.relates_to.rel_type, we'll first try data["m"], which will fail,
+ // then combine m and relates_to to get data["m.relates_to"], which should succeed.
+ path[1] = path[0] + "." + path[1]
+ return hackyNestedGet(data, path[1:])
+}
+
+func stringifyForPushCondition(val interface{}) string {
+ // Implement MSC3862 to allow matching any type of field
+ // https://github.com/matrix-org/matrix-spec-proposals/pull/3862
+ switch typedVal := val.(type) {
+ case string:
+ return typedVal
+ case nil:
+ return "null"
+ case float64:
+ // Floats aren't allowed in Matrix events, but the JSON parser always stores numbers as floats,
+ // so just handle that and convert to int
+ return strconv.FormatInt(int64(typedVal), 10)
+ default:
+ return fmt.Sprint(val)
+ }
+}
+
+func (cond *PushCondition) matchValue(room Room, evt *event.Event) bool {
+ key, subkey, _ := strings.Cut(cond.Key, ".")
+
+ pattern, err := glob.Compile(cond.Pattern)
+ if err != nil {
+ return false
+ }
+
+ switch key {
+ case "type":
+ return pattern.MatchString(evt.Type.String())
+ case "sender":
+ return pattern.MatchString(string(evt.Sender))
+ case "room_id":
+ return pattern.MatchString(string(evt.RoomID))
+ case "state_key":
+ if evt.StateKey == nil {
+ return false
+ }
+ return pattern.MatchString(*evt.StateKey)
+ case "content":
+ // Split the match key with escaping to implement https://github.com/matrix-org/matrix-spec-proposals/pull/3873
+ splitKey := splitWithEscaping(subkey, '.', '\\')
+ // Then do a hacky nested get that supports combining parts for the backwards-compat part of MSC3873
+ val, ok := hackyNestedGet(evt.Content.Raw, splitKey)
+ if !ok {
+ return cond.Pattern == ""
+ }
+ return pattern.MatchString(stringifyForPushCondition(val))
+ default:
+ return false
+ }
+}
+
+func (cond *PushCondition) getRelationEventID(relatesTo *event.RelatesTo) id.EventID {
+ if relatesTo == nil {
+ return ""
+ }
+ switch cond.RelType {
+ case "":
+ return relatesTo.EventID
+ case "m.in_reply_to":
+ if relatesTo.IsFallingBack || relatesTo.InReplyTo == nil {
+ return ""
+ }
+ return relatesTo.InReplyTo.EventID
+ default:
+ if relatesTo.Type != cond.RelType {
+ return ""
+ }
+ return relatesTo.EventID
+ }
+}
+
+func (cond *PushCondition) matchRelatedEvent(room Room, evt *event.Event) bool {
+ var relatesTo *event.RelatesTo
+ if relatable, ok := evt.Content.Parsed.(event.Relatable); ok {
+ relatesTo = relatable.OptionalGetRelatesTo()
+ } else {
+ res := gjson.GetBytes(evt.Content.VeryRaw, `m\.relates_to`)
+ if res.Exists() && res.IsObject() {
+ _ = json.Unmarshal([]byte(res.Raw), &relatesTo)
+ }
+ }
+ if evtID := cond.getRelationEventID(relatesTo); evtID == "" {
+ return false
+ } else if eventfulRoom, ok := room.(EventfulRoom); !ok {
+ return false
+ } else if evt = eventfulRoom.GetEvent(relatesTo.EventID); evt == nil {
+ return false
+ } else {
+ return cond.matchValue(room, evt)
+ }
+}
+
+func (cond *PushCondition) matchDisplayName(room Room, evt *event.Event) bool {
+ displayname := room.GetOwnDisplayname()
+ if len(displayname) == 0 {
+ return false
+ }
+
+ msg, ok := evt.Content.Raw["body"].(string)
+ if !ok {
+ return false
+ }
+
+ isAcceptable := func(r uint8) bool {
+ return unicode.IsSpace(rune(r)) || unicode.IsPunct(rune(r))
+ }
+ length := len(displayname)
+ for index := strings.Index(msg, displayname); index != -1; index = strings.Index(msg, displayname) {
+ if (index <= 0 || isAcceptable(msg[index-1])) && (index+length >= len(msg) || isAcceptable(msg[index+length])) {
+ return true
+ }
+ msg = msg[index+len(displayname):]
+ }
+ return false
+}
+
+func (cond *PushCondition) matchMemberCount(room Room) bool {
+ group := MemberCountFilterRegex.FindStringSubmatch(cond.MemberCountCondition)
+ if len(group) != 3 {
+ return false
+ }
+
+ operator := group[1]
+ wantedMemberCount, _ := strconv.Atoi(group[2])
+
+ memberCount := room.GetMemberCount()
+
+ switch operator {
+ case "==", "":
+ return memberCount == wantedMemberCount
+ case ">":
+ return memberCount > wantedMemberCount
+ case ">=":
+ return memberCount >= wantedMemberCount
+ case "<":
+ return memberCount < wantedMemberCount
+ case "<=":
+ return memberCount <= wantedMemberCount
+ default:
+ // Should be impossible due to regex.
+ return false
+ }
+}
diff --git a/vendor/maunium.net/go/mautrix/pushrules/doc.go b/vendor/maunium.net/go/mautrix/pushrules/doc.go
new file mode 100644
index 00000000..19cd7745
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/pushrules/doc.go
@@ -0,0 +1,2 @@
+// Package pushrules contains utilities to parse push notification rules.
+package pushrules
diff --git a/vendor/maunium.net/go/mautrix/pushrules/glob/LICENSE b/vendor/maunium.net/go/mautrix/pushrules/glob/LICENSE
new file mode 100644
index 00000000..cb00d952
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/pushrules/glob/LICENSE
@@ -0,0 +1,22 @@
+Glob is licensed under the MIT "Expat" License:
+
+Copyright (c) 2016: Zachary Yedidia.
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/vendor/maunium.net/go/mautrix/pushrules/glob/README.md b/vendor/maunium.net/go/mautrix/pushrules/glob/README.md
new file mode 100644
index 00000000..e2e6c649
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/pushrules/glob/README.md
@@ -0,0 +1,28 @@
+# String globbing in Go
+
+[![GoDoc](https://godoc.org/github.com/zyedidia/glob?status.svg)](http://godoc.org/github.com/zyedidia/glob)
+
+This package adds support for globs in Go.
+
+It simply converts glob expressions to regexps. I try to follow the standard defined [here](http://pubs.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_13).
+
+# Example
+
+```go
+package main
+
+import "github.com/zyedidia/glob"
+
+func main() {
+ glob, err := glob.Compile("{*.go,*.c}")
+ if err != nil {
+ // Error
+ }
+
+ glob.Match([]byte("test.c")) // true
+ glob.Match([]byte("hello.go")) // true
+ glob.Match([]byte("test.d")) // false
+}
+```
+
+You can call all the same functions on a glob that you can call on a regexp.
diff --git a/vendor/maunium.net/go/mautrix/pushrules/glob/glob.go b/vendor/maunium.net/go/mautrix/pushrules/glob/glob.go
new file mode 100644
index 00000000..c270dbc5
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/pushrules/glob/glob.go
@@ -0,0 +1,108 @@
+// Package glob provides objects for matching strings with globs
+package glob
+
+import "regexp"
+
+// Glob is a wrapper of *regexp.Regexp.
+// It should contain a glob expression compiled into a regular expression.
+type Glob struct {
+ *regexp.Regexp
+}
+
+// Compile a takes a glob expression as a string and transforms it
+// into a *Glob object (which is really just a regular expression)
+// Compile also returns a possible error.
+func Compile(pattern string) (*Glob, error) {
+ r, err := globToRegex(pattern)
+ return &Glob{r}, err
+}
+
+func globToRegex(glob string) (*regexp.Regexp, error) {
+ regex := ""
+ inGroup := 0
+ inClass := 0
+ firstIndexInClass := -1
+ arr := []byte(glob)
+
+ hasGlobCharacters := false
+
+ for i := 0; i < len(arr); i++ {
+ ch := arr[i]
+
+ switch ch {
+ case '\\':
+ i++
+ if i >= len(arr) {
+ regex += "\\"
+ } else {
+ next := arr[i]
+ switch next {
+ case ',':
+ // Nothing
+ case 'Q', 'E':
+ regex += "\\\\"
+ default:
+ regex += "\\"
+ }
+ regex += string(next)
+ }
+ case '*':
+ if inClass == 0 {
+ regex += ".*"
+ } else {
+ regex += "*"
+ }
+ hasGlobCharacters = true
+ case '?':
+ if inClass == 0 {
+ regex += "."
+ } else {
+ regex += "?"
+ }
+ hasGlobCharacters = true
+ case '[':
+ inClass++
+ firstIndexInClass = i + 1
+ regex += "["
+ hasGlobCharacters = true
+ case ']':
+ inClass--
+ regex += "]"
+ case '.', '(', ')', '+', '|', '^', '$', '@', '%':
+ if inClass == 0 || (firstIndexInClass == i && ch == '^') {
+ regex += "\\"
+ }
+ regex += string(ch)
+ hasGlobCharacters = true
+ case '!':
+ if firstIndexInClass == i {
+ regex += "^"
+ } else {
+ regex += "!"
+ }
+ hasGlobCharacters = true
+ case '{':
+ inGroup++
+ regex += "("
+ hasGlobCharacters = true
+ case '}':
+ inGroup--
+ regex += ")"
+ case ',':
+ if inGroup > 0 {
+ regex += "|"
+ hasGlobCharacters = true
+ } else {
+ regex += ","
+ }
+ default:
+ regex += string(ch)
+ }
+ }
+
+ if hasGlobCharacters {
+ return regexp.Compile("^" + regex + "$")
+ } else {
+ return regexp.Compile(regex)
+ }
+}
diff --git a/vendor/maunium.net/go/mautrix/pushrules/pushrules.go b/vendor/maunium.net/go/mautrix/pushrules/pushrules.go
new file mode 100644
index 00000000..7944299a
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/pushrules/pushrules.go
@@ -0,0 +1,37 @@
+// Copyright (c) 2020 Tulir Asokan
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package pushrules
+
+import (
+ "encoding/gob"
+ "encoding/json"
+ "reflect"
+
+ "maunium.net/go/mautrix/event"
+)
+
+// EventContent represents the content of a m.push_rules account data event.
+// https://spec.matrix.org/v1.2/client-server-api/#mpush_rules
+type EventContent struct {
+ Ruleset *PushRuleset `json:"global"`
+}
+
+func init() {
+ event.TypeMap[event.AccountDataPushRules] = reflect.TypeOf(EventContent{})
+ gob.Register(&EventContent{})
+}
+
+// EventToPushRules converts a m.push_rules event to a PushRuleset by passing the data through JSON.
+func EventToPushRules(evt *event.Event) (*PushRuleset, error) {
+ content := &EventContent{}
+ err := json.Unmarshal(evt.Content.VeryRaw, content)
+ if err != nil {
+ return nil, err
+ }
+
+ return content.Ruleset, nil
+}
diff --git a/vendor/maunium.net/go/mautrix/pushrules/rule.go b/vendor/maunium.net/go/mautrix/pushrules/rule.go
new file mode 100644
index 00000000..8ce2da77
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/pushrules/rule.go
@@ -0,0 +1,154 @@
+// Copyright (c) 2020 Tulir Asokan
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package pushrules
+
+import (
+ "encoding/gob"
+
+ "maunium.net/go/mautrix/event"
+ "maunium.net/go/mautrix/id"
+ "maunium.net/go/mautrix/pushrules/glob"
+)
+
+func init() {
+ gob.Register(PushRuleArray{})
+ gob.Register(PushRuleMap{})
+}
+
+type PushRuleCollection interface {
+ GetActions(room Room, evt *event.Event) PushActionArray
+}
+
+type PushRuleArray []*PushRule
+
+func (rules PushRuleArray) SetType(typ PushRuleType) PushRuleArray {
+ for _, rule := range rules {
+ rule.Type = typ
+ }
+ return rules
+}
+
+func (rules PushRuleArray) GetActions(room Room, evt *event.Event) PushActionArray {
+ for _, rule := range rules {
+ if !rule.Match(room, evt) {
+ continue
+ }
+ return rule.Actions
+ }
+ return nil
+}
+
+type PushRuleMap struct {
+ Map map[string]*PushRule
+ Type PushRuleType
+}
+
+func (rules PushRuleArray) SetTypeAndMap(typ PushRuleType) PushRuleMap {
+ data := PushRuleMap{
+ Map: make(map[string]*PushRule),
+ Type: typ,
+ }
+ for _, rule := range rules {
+ rule.Type = typ
+ data.Map[rule.RuleID] = rule
+ }
+ return data
+}
+
+func (ruleMap PushRuleMap) GetActions(room Room, evt *event.Event) PushActionArray {
+ var rule *PushRule
+ var found bool
+ switch ruleMap.Type {
+ case RoomRule:
+ rule, found = ruleMap.Map[string(evt.RoomID)]
+ case SenderRule:
+ rule, found = ruleMap.Map[string(evt.Sender)]
+ }
+ if found && rule.Match(room, evt) {
+ return rule.Actions
+ }
+ return nil
+}
+
+func (ruleMap PushRuleMap) Unmap() PushRuleArray {
+ array := make(PushRuleArray, len(ruleMap.Map))
+ index := 0
+ for _, rule := range ruleMap.Map {
+ array[index] = rule
+ index++
+ }
+ return array
+}
+
+type PushRuleType string
+
+const (
+ OverrideRule PushRuleType = "override"
+ ContentRule PushRuleType = "content"
+ RoomRule PushRuleType = "room"
+ SenderRule PushRuleType = "sender"
+ UnderrideRule PushRuleType = "underride"
+)
+
+type PushRule struct {
+ // The type of this rule.
+ Type PushRuleType `json:"-"`
+ // The ID of this rule.
+ // For room-specific rules and user-specific rules, this is the room or user ID (respectively)
+ // For other types of rules, this doesn't affect anything.
+ RuleID string `json:"rule_id"`
+ // The actions this rule should trigger when matched.
+ Actions PushActionArray `json:"actions"`
+ // Whether this is a default rule, or has been set explicitly.
+ Default bool `json:"default"`
+ // Whether or not this push rule is enabled.
+ Enabled bool `json:"enabled"`
+ // The conditions to match in order to trigger this rule.
+ // Only applicable to generic underride/override rules.
+ Conditions []*PushCondition `json:"conditions,omitempty"`
+ // Pattern for content-specific push rules
+ Pattern string `json:"pattern,omitempty"`
+}
+
+func (rule *PushRule) Match(room Room, evt *event.Event) bool {
+ if !rule.Enabled {
+ return false
+ }
+ switch rule.Type {
+ case OverrideRule, UnderrideRule:
+ return rule.matchConditions(room, evt)
+ case ContentRule:
+ return rule.matchPattern(room, evt)
+ case RoomRule:
+ return id.RoomID(rule.RuleID) == evt.RoomID
+ case SenderRule:
+ return id.UserID(rule.RuleID) == evt.Sender
+ default:
+ return false
+ }
+}
+
+func (rule *PushRule) matchConditions(room Room, evt *event.Event) bool {
+ for _, cond := range rule.Conditions {
+ if !cond.Match(room, evt) {
+ return false
+ }
+ }
+ return true
+}
+
+func (rule *PushRule) matchPattern(room Room, evt *event.Event) bool {
+ pattern, err := glob.Compile(rule.Pattern)
+ if err != nil {
+ return false
+ }
+ msg, ok := evt.Content.Raw["body"].(string)
+ if !ok {
+ return false
+ }
+ return pattern.MatchString(msg)
+}
diff --git a/vendor/maunium.net/go/mautrix/pushrules/ruleset.go b/vendor/maunium.net/go/mautrix/pushrules/ruleset.go
new file mode 100644
index 00000000..48ae1e35
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/pushrules/ruleset.go
@@ -0,0 +1,88 @@
+// Copyright (c) 2020 Tulir Asokan
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package pushrules
+
+import (
+ "encoding/json"
+
+ "maunium.net/go/mautrix/event"
+)
+
+type PushRuleset struct {
+ Override PushRuleArray
+ Content PushRuleArray
+ Room PushRuleMap
+ Sender PushRuleMap
+ Underride PushRuleArray
+}
+
+type rawPushRuleset struct {
+ Override PushRuleArray `json:"override"`
+ Content PushRuleArray `json:"content"`
+ Room PushRuleArray `json:"room"`
+ Sender PushRuleArray `json:"sender"`
+ Underride PushRuleArray `json:"underride"`
+}
+
+// UnmarshalJSON parses JSON into this PushRuleset.
+//
+// For override, sender and underride push rule arrays, the type is added
+// to each PushRule and the array is used as-is.
+//
+// For room and sender push rule arrays, the type is added to each PushRule
+// and the array is converted to a map with the rule ID as the key and the
+// PushRule as the value.
+func (rs *PushRuleset) UnmarshalJSON(raw []byte) (err error) {
+ data := rawPushRuleset{}
+ err = json.Unmarshal(raw, &data)
+ if err != nil {
+ return
+ }
+
+ rs.Override = data.Override.SetType(OverrideRule)
+ rs.Content = data.Content.SetType(ContentRule)
+ rs.Room = data.Room.SetTypeAndMap(RoomRule)
+ rs.Sender = data.Sender.SetTypeAndMap(SenderRule)
+ rs.Underride = data.Underride.SetType(UnderrideRule)
+ return
+}
+
+// MarshalJSON is the reverse of UnmarshalJSON()
+func (rs *PushRuleset) MarshalJSON() ([]byte, error) {
+ data := rawPushRuleset{
+ Override: rs.Override,
+ Content: rs.Content,
+ Room: rs.Room.Unmap(),
+ Sender: rs.Sender.Unmap(),
+ Underride: rs.Underride,
+ }
+ return json.Marshal(&data)
+}
+
+// DefaultPushActions is the value returned if none of the rule
+// collections in a Ruleset match the event given to GetActions()
+var DefaultPushActions = PushActionArray{&PushAction{Action: ActionDontNotify}}
+
+// GetActions matches the given event against all of the push rule
+// collections in this push ruleset in the order of priority as
+// specified in spec section 11.12.1.4.
+func (rs *PushRuleset) GetActions(room Room, evt *event.Event) (match PushActionArray) {
+ // Add push rule collections to array in priority order
+ arrays := []PushRuleCollection{rs.Override, rs.Content, rs.Room, rs.Sender, rs.Underride}
+ // Loop until one of the push rule collections matches the room/event combo.
+ for _, pra := range arrays {
+ if pra == nil {
+ continue
+ }
+ if match = pra.GetActions(room, evt); match != nil {
+ // Match found, return it.
+ return
+ }
+ }
+ // No match found, return default actions.
+ return DefaultPushActions
+}
diff --git a/vendor/maunium.net/go/mautrix/requests.go b/vendor/maunium.net/go/mautrix/requests.go
new file mode 100644
index 00000000..8c7571de
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/requests.go
@@ -0,0 +1,437 @@
+package mautrix
+
+import (
+ "encoding/json"
+ "strconv"
+
+ "maunium.net/go/mautrix/event"
+ "maunium.net/go/mautrix/id"
+ "maunium.net/go/mautrix/pushrules"
+)
+
+type AuthType string
+
+const (
+ AuthTypePassword AuthType = "m.login.password"
+ AuthTypeReCAPTCHA AuthType = "m.login.recaptcha"
+ AuthTypeOAuth2 AuthType = "m.login.oauth2"
+ AuthTypeSSO AuthType = "m.login.sso"
+ AuthTypeEmail AuthType = "m.login.email.identity"
+ AuthTypeMSISDN AuthType = "m.login.msisdn"
+ AuthTypeToken AuthType = "m.login.token"
+ AuthTypeDummy AuthType = "m.login.dummy"
+ AuthTypeAppservice AuthType = "m.login.application_service"
+)
+
+type IdentifierType string
+
+const (
+ IdentifierTypeUser = "m.id.user"
+ IdentifierTypeThirdParty = "m.id.thirdparty"
+ IdentifierTypePhone = "m.id.phone"
+)
+
+type Direction rune
+
+const (
+ DirectionForward Direction = 'f'
+ DirectionBackward Direction = 'b'
+)
+
+// ReqRegister is the JSON request for https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3register
+type ReqRegister struct {
+ Username string `json:"username,omitempty"`
+ Password string `json:"password,omitempty"`
+ DeviceID id.DeviceID `json:"device_id,omitempty"`
+ InitialDeviceDisplayName string `json:"initial_device_display_name,omitempty"`
+ InhibitLogin bool `json:"inhibit_login,omitempty"`
+ RefreshToken bool `json:"refresh_token,omitempty"`
+ Auth interface{} `json:"auth,omitempty"`
+
+ // Type for registration, only used for appservice user registrations
+ // https://spec.matrix.org/v1.2/application-service-api/#server-admin-style-permissions
+ Type AuthType `json:"type,omitempty"`
+}
+
+type BaseAuthData struct {
+ Type AuthType `json:"type"`
+ Session string `json:"session,omitempty"`
+}
+
+type UserIdentifier struct {
+ Type IdentifierType `json:"type"`
+
+ User string `json:"user,omitempty"`
+
+ Medium string `json:"medium,omitempty"`
+ Address string `json:"address,omitempty"`
+
+ Country string `json:"country,omitempty"`
+ Phone string `json:"phone,omitempty"`
+}
+
+// ReqLogin is the JSON request for https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3login
+type ReqLogin struct {
+ Type AuthType `json:"type"`
+ Identifier UserIdentifier `json:"identifier"`
+ Password string `json:"password,omitempty"`
+ Token string `json:"token,omitempty"`
+ DeviceID id.DeviceID `json:"device_id,omitempty"`
+ InitialDeviceDisplayName string `json:"initial_device_display_name,omitempty"`
+
+ // Whether or not the returned credentials should be stored in the Client
+ StoreCredentials bool `json:"-"`
+ // Whether or not the returned .well-known data should update the homeserver URL in the Client
+ StoreHomeserverURL bool `json:"-"`
+}
+
+type ReqUIAuthFallback struct {
+ Session string `json:"session"`
+ User string `json:"user"`
+}
+
+type ReqUIAuthLogin struct {
+ BaseAuthData
+ User string `json:"user"`
+ Password string `json:"password"`
+}
+
+// ReqCreateRoom is the JSON request for https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3createroom
+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 []id.UserID `json:"invite,omitempty"`
+ Invite3PID []ReqInvite3PID `json:"invite_3pid,omitempty"`
+ CreationContent map[string]interface{} `json:"creation_content,omitempty"`
+ InitialState []*event.Event `json:"initial_state,omitempty"`
+ Preset string `json:"preset,omitempty"`
+ IsDirect bool `json:"is_direct,omitempty"`
+ RoomVersion string `json:"room_version,omitempty"`
+
+ PowerLevelOverride *event.PowerLevelsEventContent `json:"power_level_content_override,omitempty"`
+
+ MeowRoomID id.RoomID `json:"fi.mau.room_id,omitempty"`
+ BeeperAutoJoinInvites bool `json:"com.beeper.auto_join_invites,omitempty"`
+}
+
+// ReqRedact is the JSON request for https://spec.matrix.org/v1.2/client-server-api/#put_matrixclientv3roomsroomidredacteventidtxnid
+type ReqRedact struct {
+ Reason string
+ TxnID string
+ Extra map[string]interface{}
+}
+
+type ReqMembers struct {
+ At string `json:"at"`
+ Membership event.Membership `json:"membership,omitempty"`
+ NotMembership event.Membership `json:"not_membership,omitempty"`
+}
+
+// ReqInvite3PID is the JSON request for https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3roomsroomidinvite-1
+// It is also a JSON object used in https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3createroom
+type ReqInvite3PID struct {
+ IDServer string `json:"id_server"`
+ Medium string `json:"medium"`
+ Address string `json:"address"`
+}
+
+type ReqLeave struct {
+ Reason string `json:"reason,omitempty"`
+}
+
+// ReqInviteUser is the JSON request for https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3roomsroomidinvite
+type ReqInviteUser struct {
+ Reason string `json:"reason,omitempty"`
+ UserID id.UserID `json:"user_id"`
+}
+
+// ReqKickUser is the JSON request for https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3roomsroomidkick
+type ReqKickUser struct {
+ Reason string `json:"reason,omitempty"`
+ UserID id.UserID `json:"user_id"`
+}
+
+// ReqBanUser is the JSON request for https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3roomsroomidban
+type ReqBanUser struct {
+ Reason string `json:"reason,omitempty"`
+ UserID id.UserID `json:"user_id"`
+}
+
+// ReqUnbanUser is the JSON request for https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3roomsroomidunban
+type ReqUnbanUser struct {
+ Reason string `json:"reason,omitempty"`
+ UserID id.UserID `json:"user_id"`
+}
+
+// ReqTyping is the JSON request for https://spec.matrix.org/v1.2/client-server-api/#put_matrixclientv3roomsroomidtypinguserid
+type ReqTyping struct {
+ Typing bool `json:"typing"`
+ Timeout int64 `json:"timeout,omitempty"`
+}
+
+type ReqPresence struct {
+ Presence event.Presence `json:"presence"`
+}
+
+type ReqAliasCreate struct {
+ RoomID id.RoomID `json:"room_id"`
+}
+
+type OneTimeKey struct {
+ Key id.Curve25519 `json:"key"`
+ Fallback bool `json:"fallback,omitempty"`
+ Signatures Signatures `json:"signatures,omitempty"`
+ Unsigned map[string]any `json:"unsigned,omitempty"`
+ IsSigned bool `json:"-"`
+
+ // Raw data in the one-time key. This must be used for signature verification to ensure unrecognized fields
+ // aren't thrown away (because that would invalidate the signature).
+ RawData json.RawMessage `json:"-"`
+}
+
+type serializableOTK OneTimeKey
+
+func (otk *OneTimeKey) UnmarshalJSON(data []byte) (err error) {
+ if len(data) > 0 && data[0] == '"' && data[len(data)-1] == '"' {
+ err = json.Unmarshal(data, &otk.Key)
+ otk.Signatures = nil
+ otk.Unsigned = nil
+ otk.IsSigned = false
+ } else {
+ err = json.Unmarshal(data, (*serializableOTK)(otk))
+ otk.RawData = data
+ otk.IsSigned = true
+ }
+ return err
+}
+
+func (otk *OneTimeKey) MarshalJSON() ([]byte, error) {
+ if !otk.IsSigned {
+ return json.Marshal(otk.Key)
+ } else {
+ return json.Marshal((*serializableOTK)(otk))
+ }
+}
+
+type ReqUploadKeys struct {
+ DeviceKeys *DeviceKeys `json:"device_keys,omitempty"`
+ OneTimeKeys map[id.KeyID]OneTimeKey `json:"one_time_keys"`
+}
+
+type ReqKeysSignatures struct {
+ UserID id.UserID `json:"user_id"`
+ DeviceID id.DeviceID `json:"device_id,omitempty"`
+ Algorithms []id.Algorithm `json:"algorithms,omitempty"`
+ Usage []id.CrossSigningUsage `json:"usage,omitempty"`
+ Keys map[id.KeyID]string `json:"keys"`
+ Signatures Signatures `json:"signatures"`
+}
+
+type ReqUploadSignatures map[id.UserID]map[string]ReqKeysSignatures
+
+type DeviceKeys struct {
+ UserID id.UserID `json:"user_id"`
+ DeviceID id.DeviceID `json:"device_id"`
+ Algorithms []id.Algorithm `json:"algorithms"`
+ Keys KeyMap `json:"keys"`
+ Signatures Signatures `json:"signatures"`
+ Unsigned map[string]interface{} `json:"unsigned,omitempty"`
+}
+
+type CrossSigningKeys struct {
+ UserID id.UserID `json:"user_id"`
+ Usage []id.CrossSigningUsage `json:"usage"`
+ Keys map[id.KeyID]id.Ed25519 `json:"keys"`
+ Signatures map[id.UserID]map[id.KeyID]string `json:"signatures,omitempty"`
+}
+
+func (csk *CrossSigningKeys) FirstKey() id.Ed25519 {
+ for _, key := range csk.Keys {
+ return key
+ }
+ return ""
+}
+
+type UploadCrossSigningKeysReq struct {
+ Master CrossSigningKeys `json:"master_key"`
+ SelfSigning CrossSigningKeys `json:"self_signing_key"`
+ UserSigning CrossSigningKeys `json:"user_signing_key"`
+ Auth interface{} `json:"auth,omitempty"`
+}
+
+type KeyMap map[id.DeviceKeyID]string
+
+func (km KeyMap) GetEd25519(deviceID id.DeviceID) id.Ed25519 {
+ val, ok := km[id.NewDeviceKeyID(id.KeyAlgorithmEd25519, deviceID)]
+ if !ok {
+ return ""
+ }
+ return id.Ed25519(val)
+}
+
+func (km KeyMap) GetCurve25519(deviceID id.DeviceID) id.Curve25519 {
+ val, ok := km[id.NewDeviceKeyID(id.KeyAlgorithmCurve25519, deviceID)]
+ if !ok {
+ return ""
+ }
+ return id.Curve25519(val)
+}
+
+type Signatures map[id.UserID]map[id.KeyID]string
+
+type ReqQueryKeys struct {
+ DeviceKeys DeviceKeysRequest `json:"device_keys"`
+
+ Timeout int64 `json:"timeout,omitempty"`
+ Token string `json:"token,omitempty"`
+}
+
+type DeviceKeysRequest map[id.UserID]DeviceIDList
+
+type DeviceIDList []id.DeviceID
+
+type ReqClaimKeys struct {
+ OneTimeKeys OneTimeKeysRequest `json:"one_time_keys"`
+
+ Timeout int64 `json:"timeout,omitempty"`
+}
+
+type OneTimeKeysRequest map[id.UserID]map[id.DeviceID]id.KeyAlgorithm
+
+type ReqSendToDevice struct {
+ Messages map[id.UserID]map[id.DeviceID]*event.Content `json:"messages"`
+}
+
+// ReqDeviceInfo is the JSON request for https://spec.matrix.org/v1.2/client-server-api/#put_matrixclientv3devicesdeviceid
+type ReqDeviceInfo struct {
+ DisplayName string `json:"display_name,omitempty"`
+}
+
+// ReqDeleteDevice is the JSON request for https://spec.matrix.org/v1.2/client-server-api/#delete_matrixclientv3devicesdeviceid
+type ReqDeleteDevice struct {
+ Auth interface{} `json:"auth,omitempty"`
+}
+
+// ReqDeleteDevices is the JSON request for https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3delete_devices
+type ReqDeleteDevices struct {
+ Devices []id.DeviceID `json:"devices"`
+ Auth interface{} `json:"auth,omitempty"`
+}
+
+type ReqPutPushRule struct {
+ Before string `json:"-"`
+ After string `json:"-"`
+
+ Actions []pushrules.PushActionType `json:"actions"`
+ Conditions []pushrules.PushCondition `json:"conditions"`
+ Pattern string `json:"pattern"`
+}
+
+type ReqBatchSend struct {
+ PrevEventID id.EventID `json:"-"`
+ BatchID id.BatchID `json:"-"`
+
+ BeeperNewMessages bool `json:"-"`
+ BeeperMarkReadBy id.UserID `json:"-"`
+
+ StateEventsAtStart []*event.Event `json:"state_events_at_start"`
+ Events []*event.Event `json:"events"`
+}
+
+type ReqSetReadMarkers struct {
+ Read id.EventID `json:"m.read,omitempty"`
+ ReadPrivate id.EventID `json:"m.read.private,omitempty"`
+ FullyRead id.EventID `json:"m.fully_read,omitempty"`
+
+ BeeperReadExtra interface{} `json:"com.beeper.read.extra,omitempty"`
+ BeeperReadPrivateExtra interface{} `json:"com.beeper.read.private.extra,omitempty"`
+ BeeperFullyReadExtra interface{} `json:"com.beeper.fully_read.extra,omitempty"`
+}
+
+type ReqSendReceipt struct {
+ ThreadID string `json:"thread_id,omitempty"`
+}
+
+// ReqHierarchy contains the parameters for https://spec.matrix.org/v1.4/client-server-api/#get_matrixclientv1roomsroomidhierarchy
+//
+// As it's a GET method, there is no JSON body, so this is only query parameters.
+type ReqHierarchy struct {
+ // A pagination token from a previous Hierarchy call.
+ // If specified, max_depth and suggested_only cannot be changed from the first request.
+ From string
+ // Limit for the maximum number of rooms to include per response.
+ // The server will apply a default value if a limit isn't provided.
+ Limit int
+ // Limit for how far to go into the space. When reached, no further child rooms will be returned.
+ // The server will apply a default value if a max depth isn't provided.
+ MaxDepth *int
+ // Flag to indicate whether the server should only consider suggested rooms.
+ // Suggested rooms are annotated in their m.space.child event contents.
+ SuggestedOnly bool
+}
+
+func (req *ReqHierarchy) Query() map[string]string {
+ query := map[string]string{}
+ if req == nil {
+ return query
+ }
+ if req.From != "" {
+ query["from"] = req.From
+ }
+ if req.Limit > 0 {
+ query["limit"] = strconv.Itoa(req.Limit)
+ }
+ if req.MaxDepth != nil {
+ query["max_depth"] = strconv.Itoa(*req.MaxDepth)
+ }
+ if req.SuggestedOnly {
+ query["suggested_only"] = "true"
+ }
+ return query
+}
+
+type ReqAppservicePing struct {
+ TxnID string `json:"transaction_id,omitempty"`
+}
+
+type ReqBeeperMergeRoom struct {
+ NewRoom ReqCreateRoom `json:"create"`
+ Key string `json:"key"`
+ Rooms []id.RoomID `json:"rooms"`
+ User id.UserID `json:"user_id"`
+}
+
+type BeeperSplitRoomPart struct {
+ UserID id.UserID `json:"user_id"`
+ Values []string `json:"values"`
+ NewRoom ReqCreateRoom `json:"create"`
+}
+
+type ReqBeeperSplitRoom struct {
+ RoomID id.RoomID `json:"-"`
+
+ Key string `json:"key"`
+ Parts []BeeperSplitRoomPart `json:"parts"`
+}
+
+type ReqRoomKeysVersionCreate struct {
+ Algorithm string `json:"algorithm"`
+ AuthData json.RawMessage `json:"auth_data"`
+}
+
+type ReqRoomKeysUpdate struct {
+ Rooms map[id.RoomID]ReqRoomKeysRoomUpdate `json:"rooms"`
+}
+
+type ReqRoomKeysRoomUpdate struct {
+ Sessions map[id.SessionID]ReqRoomKeysSessionUpdate `json:"sessions"`
+}
+
+type ReqRoomKeysSessionUpdate struct {
+ FirstMessageIndex int `json:"first_message_index"`
+ ForwardedCount int `json:"forwarded_count"`
+ IsVerified bool `json:"is_verified"`
+ SessionData json.RawMessage `json:"session_data"`
+}
diff --git a/vendor/maunium.net/go/mautrix/responses.go b/vendor/maunium.net/go/mautrix/responses.go
new file mode 100644
index 00000000..ac6bbfe7
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/responses.go
@@ -0,0 +1,600 @@
+package mautrix
+
+import (
+ "bytes"
+ "encoding/json"
+ "reflect"
+ "strconv"
+ "strings"
+
+ "github.com/tidwall/gjson"
+
+ "maunium.net/go/mautrix/event"
+ "maunium.net/go/mautrix/id"
+ "maunium.net/go/mautrix/util"
+ "maunium.net/go/mautrix/util/jsontime"
+)
+
+// RespWhoami is the JSON response for https://spec.matrix.org/v1.2/client-server-api/#get_matrixclientv3accountwhoami
+type RespWhoami struct {
+ UserID id.UserID `json:"user_id"`
+ DeviceID id.DeviceID `json:"device_id"`
+}
+
+// RespCreateFilter is the JSON response for https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3useruseridfilter
+type RespCreateFilter struct {
+ FilterID string `json:"filter_id"`
+}
+
+// RespJoinRoom is the JSON response for https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3roomsroomidjoin
+type RespJoinRoom struct {
+ RoomID id.RoomID `json:"room_id"`
+}
+
+// RespLeaveRoom is the JSON response for https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3roomsroomidleave
+type RespLeaveRoom struct{}
+
+// RespForgetRoom is the JSON response for https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3roomsroomidforget
+type RespForgetRoom struct{}
+
+// RespInviteUser is the JSON response for https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3roomsroomidinvite
+type RespInviteUser struct{}
+
+// RespKickUser is the JSON response for https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3roomsroomidkick
+type RespKickUser struct{}
+
+// RespBanUser is the JSON response for https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3roomsroomidban
+type RespBanUser struct{}
+
+// RespUnbanUser is the JSON response for https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3roomsroomidunban
+type RespUnbanUser struct{}
+
+// RespTyping is the JSON response for https://spec.matrix.org/v1.2/client-server-api/#put_matrixclientv3roomsroomidtypinguserid
+type RespTyping struct{}
+
+// RespPresence is the JSON response for https://spec.matrix.org/v1.2/client-server-api/#get_matrixclientv3presenceuseridstatus
+type RespPresence struct {
+ Presence event.Presence `json:"presence"`
+ LastActiveAgo int `json:"last_active_ago"`
+ StatusMsg string `json:"status_msg"`
+ CurrentlyActive bool `json:"currently_active"`
+}
+
+// RespJoinedRooms is the JSON response for https://spec.matrix.org/v1.2/client-server-api/#get_matrixclientv3joined_rooms
+type RespJoinedRooms struct {
+ JoinedRooms []id.RoomID `json:"joined_rooms"`
+}
+
+// RespJoinedMembers is the JSON response for https://spec.matrix.org/v1.2/client-server-api/#get_matrixclientv3roomsroomidjoined_members
+type RespJoinedMembers struct {
+ Joined map[id.UserID]JoinedMember `json:"joined"`
+}
+
+type JoinedMember struct {
+ DisplayName string `json:"display_name,omitempty"`
+ AvatarURL string `json:"avatar_url,omitempty"`
+}
+
+// RespMessages is the JSON response for https://spec.matrix.org/v1.2/client-server-api/#get_matrixclientv3roomsroomidmessages
+type RespMessages struct {
+ Start string `json:"start"`
+ Chunk []*event.Event `json:"chunk"`
+ State []*event.Event `json:"state"`
+ End string `json:"end,omitempty"`
+}
+
+// RespContext is the JSON response for https://spec.matrix.org/v1.2/client-server-api/#get_matrixclientv3roomsroomidcontexteventid
+type RespContext struct {
+ End string `json:"end"`
+ Event *event.Event `json:"event"`
+ EventsAfter []*event.Event `json:"events_after"`
+ EventsBefore []*event.Event `json:"events_before"`
+ Start string `json:"start"`
+ State []*event.Event `json:"state"`
+}
+
+// RespSendEvent is the JSON response for https://spec.matrix.org/v1.2/client-server-api/#put_matrixclientv3roomsroomidsendeventtypetxnid
+type RespSendEvent struct {
+ EventID id.EventID `json:"event_id"`
+}
+
+// RespMediaConfig is the JSON response for https://spec.matrix.org/v1.4/client-server-api/#get_matrixmediav3config
+type RespMediaConfig struct {
+ UploadSize int64 `json:"m.upload.size,omitempty"`
+}
+
+// RespMediaUpload is the JSON response for https://spec.matrix.org/v1.2/client-server-api/#post_matrixmediav3upload
+type RespMediaUpload struct {
+ ContentURI id.ContentURI `json:"content_uri"`
+}
+
+// RespCreateMXC is the JSON response for /_matrix/media/v3/create as specified in https://github.com/matrix-org/matrix-spec-proposals/pull/2246
+type RespCreateMXC struct {
+ ContentURI id.ContentURI `json:"content_uri"`
+ UnusedExpiresAt int `json:"unused_expires_at,omitempty"`
+ UploadURL string `json:"upload_url,omitempty"`
+}
+
+// RespPreviewURL is the JSON response for https://spec.matrix.org/v1.2/client-server-api/#get_matrixmediav3preview_url
+type RespPreviewURL struct {
+ CanonicalURL string `json:"og:url,omitempty"`
+ Title string `json:"og:title,omitempty"`
+ Type string `json:"og:type,omitempty"`
+ Description string `json:"og:description,omitempty"`
+
+ ImageURL id.ContentURIString `json:"og:image,omitempty"`
+
+ ImageSize int `json:"matrix:image:size,omitempty"`
+ ImageWidth int `json:"og:image:width,omitempty"`
+ ImageHeight int `json:"og:image:height,omitempty"`
+ ImageType string `json:"og:image:type,omitempty"`
+}
+
+// RespUserInteractive is the JSON response for https://spec.matrix.org/v1.2/client-server-api/#user-interactive-authentication-api
+type RespUserInteractive struct {
+ Flows []UIAFlow `json:"flows,omitempty"`
+ Params map[AuthType]interface{} `json:"params,omitempty"`
+ Session string `json:"session,omitempty"`
+ Completed []string `json:"completed,omitempty"`
+
+ ErrCode string `json:"errcode,omitempty"`
+ Error string `json:"error,omitempty"`
+}
+
+type UIAFlow struct {
+ Stages []AuthType `json:"stages,omitempty"`
+}
+
+// HasSingleStageFlow returns true if there exists at least 1 Flow with a single stage of stageName.
+func (r RespUserInteractive) HasSingleStageFlow(stageName AuthType) 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://spec.matrix.org/v1.2/client-server-api/#get_matrixclientv3profileuseriddisplayname
+type RespUserDisplayName struct {
+ DisplayName string `json:"displayname"`
+}
+
+type RespUserProfile struct {
+ DisplayName string `json:"displayname"`
+ AvatarURL id.ContentURI `json:"avatar_url"`
+}
+
+// RespRegisterAvailable is the JSON response for https://spec.matrix.org/v1.4/client-server-api/#get_matrixclientv3registeravailable
+type RespRegisterAvailable struct {
+ Available bool `json:"available"`
+}
+
+// RespRegister is the JSON response for https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3register
+type RespRegister struct {
+ AccessToken string `json:"access_token,omitempty"`
+ DeviceID id.DeviceID `json:"device_id,omitempty"`
+ UserID id.UserID `json:"user_id"`
+
+ RefreshToken string `json:"refresh_token,omitempty"`
+ ExpiresInMS int64 `json:"expires_in_ms,omitempty"`
+
+ // Deprecated: homeserver should be parsed from the user ID
+ HomeServer string `json:"home_server,omitempty"`
+}
+
+type LoginFlow struct {
+ Type AuthType `json:"type"`
+}
+
+// RespLoginFlows is the JSON response for https://spec.matrix.org/v1.2/client-server-api/#get_matrixclientv3login
+type RespLoginFlows struct {
+ Flows []LoginFlow `json:"flows"`
+}
+
+func (rlf *RespLoginFlows) FirstFlowOfType(flowTypes ...AuthType) *LoginFlow {
+ for _, flow := range rlf.Flows {
+ for _, flowType := range flowTypes {
+ if flow.Type == flowType {
+ return &flow
+ }
+ }
+ }
+ return nil
+}
+
+func (rlf *RespLoginFlows) HasFlow(flowType ...AuthType) bool {
+ return rlf.FirstFlowOfType(flowType...) != nil
+}
+
+// RespLogin is the JSON response for https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3login
+type RespLogin struct {
+ AccessToken string `json:"access_token"`
+ DeviceID id.DeviceID `json:"device_id"`
+ UserID id.UserID `json:"user_id"`
+ WellKnown *ClientWellKnown `json:"well_known,omitempty"`
+}
+
+// RespLogout is the JSON response for https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3logout
+type RespLogout struct{}
+
+// RespCreateRoom is the JSON response for https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3createroom
+type RespCreateRoom struct {
+ RoomID id.RoomID `json:"room_id"`
+}
+
+type RespMembers struct {
+ Chunk []*event.Event `json:"chunk"`
+}
+
+type LazyLoadSummary struct {
+ Heroes []id.UserID `json:"m.heroes,omitempty"`
+ JoinedMemberCount *int `json:"m.joined_member_count,omitempty"`
+ InvitedMemberCount *int `json:"m.invited_member_count,omitempty"`
+}
+
+type SyncEventsList struct {
+ Events []*event.Event `json:"events,omitempty"`
+}
+
+type SyncTimeline struct {
+ SyncEventsList
+ Limited bool `json:"limited,omitempty"`
+ PrevBatch string `json:"prev_batch,omitempty"`
+}
+
+// RespSync is the JSON response for https://spec.matrix.org/v1.2/client-server-api/#get_matrixclientv3sync
+type RespSync struct {
+ NextBatch string `json:"next_batch"`
+
+ AccountData SyncEventsList `json:"account_data"`
+ Presence SyncEventsList `json:"presence"`
+ ToDevice SyncEventsList `json:"to_device"`
+
+ DeviceLists DeviceLists `json:"device_lists"`
+ DeviceOTKCount OTKCount `json:"device_one_time_keys_count"`
+ FallbackKeys []id.KeyAlgorithm `json:"device_unused_fallback_key_types"`
+
+ Rooms RespSyncRooms `json:"rooms"`
+}
+
+type RespSyncRooms struct {
+ Leave map[id.RoomID]*SyncLeftRoom `json:"leave,omitempty"`
+ Join map[id.RoomID]*SyncJoinedRoom `json:"join,omitempty"`
+ Invite map[id.RoomID]*SyncInvitedRoom `json:"invite,omitempty"`
+ Knock map[id.RoomID]*SyncKnockedRoom `json:"knock,omitempty"`
+}
+
+type marshalableRespSync RespSync
+
+var syncPathsToDelete = []string{"account_data", "presence", "to_device", "device_lists", "device_one_time_keys_count", "rooms"}
+
+func (rs *RespSync) MarshalJSON() ([]byte, error) {
+ return util.MarshalAndDeleteEmpty((*marshalableRespSync)(rs), syncPathsToDelete)
+}
+
+type DeviceLists struct {
+ Changed []id.UserID `json:"changed,omitempty"`
+ Left []id.UserID `json:"left,omitempty"`
+}
+
+type OTKCount struct {
+ Curve25519 int `json:"curve25519,omitempty"`
+ SignedCurve25519 int `json:"signed_curve25519,omitempty"`
+
+ // For appservice OTK counts only: the user ID in question
+ UserID id.UserID `json:"-"`
+ DeviceID id.DeviceID `json:"-"`
+}
+
+type SyncLeftRoom struct {
+ Summary LazyLoadSummary `json:"summary"`
+ State SyncEventsList `json:"state"`
+ Timeline SyncTimeline `json:"timeline"`
+}
+
+type marshalableSyncLeftRoom SyncLeftRoom
+
+var syncLeftRoomPathsToDelete = []string{"summary", "state", "timeline"}
+
+func (slr SyncLeftRoom) MarshalJSON() ([]byte, error) {
+ return util.MarshalAndDeleteEmpty((marshalableSyncLeftRoom)(slr), syncLeftRoomPathsToDelete)
+}
+
+type SyncJoinedRoom struct {
+ Summary LazyLoadSummary `json:"summary"`
+ State SyncEventsList `json:"state"`
+ Timeline SyncTimeline `json:"timeline"`
+ Ephemeral SyncEventsList `json:"ephemeral"`
+ AccountData SyncEventsList `json:"account_data"`
+
+ UnreadNotifications *UnreadNotificationCounts `json:"unread_notifications,omitempty"`
+ // https://github.com/matrix-org/matrix-spec-proposals/pull/2654
+ MSC2654UnreadCount *int `json:"org.matrix.msc2654.unread_count,omitempty"`
+}
+
+type UnreadNotificationCounts struct {
+ HighlightCount int `json:"highlight_count"`
+ NotificationCount int `json:"notification_count"`
+}
+
+type marshalableSyncJoinedRoom SyncJoinedRoom
+
+var syncJoinedRoomPathsToDelete = []string{"summary", "state", "timeline", "ephemeral", "account_data"}
+
+func (sjr SyncJoinedRoom) MarshalJSON() ([]byte, error) {
+ return util.MarshalAndDeleteEmpty((marshalableSyncJoinedRoom)(sjr), syncJoinedRoomPathsToDelete)
+}
+
+type SyncInvitedRoom struct {
+ Summary LazyLoadSummary `json:"summary"`
+ State SyncEventsList `json:"invite_state"`
+}
+
+type marshalableSyncInvitedRoom SyncInvitedRoom
+
+var syncInvitedRoomPathsToDelete = []string{"summary"}
+
+func (sir SyncInvitedRoom) MarshalJSON() ([]byte, error) {
+ return util.MarshalAndDeleteEmpty((marshalableSyncInvitedRoom)(sir), syncInvitedRoomPathsToDelete)
+}
+
+type SyncKnockedRoom struct {
+ State SyncEventsList `json:"knock_state"`
+}
+
+type RespTurnServer struct {
+ Username string `json:"username"`
+ Password string `json:"password"`
+ TTL int `json:"ttl"`
+ URIs []string `json:"uris"`
+}
+
+type RespAliasCreate struct{}
+type RespAliasDelete struct{}
+type RespAliasResolve struct {
+ RoomID id.RoomID `json:"room_id"`
+ Servers []string `json:"servers"`
+}
+type RespAliasList struct {
+ Aliases []id.RoomAlias `json:"aliases"`
+}
+
+type RespUploadKeys struct {
+ OneTimeKeyCounts OTKCount `json:"one_time_key_counts"`
+}
+
+type RespQueryKeys struct {
+ Failures map[string]interface{} `json:"failures,omitempty"`
+ DeviceKeys map[id.UserID]map[id.DeviceID]DeviceKeys `json:"device_keys"`
+ MasterKeys map[id.UserID]CrossSigningKeys `json:"master_keys"`
+ SelfSigningKeys map[id.UserID]CrossSigningKeys `json:"self_signing_keys"`
+ UserSigningKeys map[id.UserID]CrossSigningKeys `json:"user_signing_keys"`
+}
+
+type RespClaimKeys struct {
+ Failures map[string]interface{} `json:"failures,omitempty"`
+ OneTimeKeys map[id.UserID]map[id.DeviceID]map[id.KeyID]OneTimeKey `json:"one_time_keys"`
+}
+
+type RespUploadSignatures struct {
+ Failures map[string]interface{} `json:"failures,omitempty"`
+}
+
+type RespKeyChanges struct {
+ Changed []id.UserID `json:"changed"`
+ Left []id.UserID `json:"left"`
+}
+
+type RespSendToDevice struct{}
+
+// RespDevicesInfo is the JSON response for https://spec.matrix.org/v1.2/client-server-api/#get_matrixclientv3devices
+type RespDevicesInfo struct {
+ Devices []RespDeviceInfo `json:"devices"`
+}
+
+// RespDeviceInfo is the JSON response for https://spec.matrix.org/v1.2/client-server-api/#get_matrixclientv3devicesdeviceid
+type RespDeviceInfo struct {
+ DeviceID id.DeviceID `json:"device_id"`
+ DisplayName string `json:"display_name"`
+ LastSeenIP string `json:"last_seen_ip"`
+ LastSeenTS int64 `json:"last_seen_ts"`
+}
+
+type RespBatchSend struct {
+ StateEventIDs []id.EventID `json:"state_event_ids"`
+ EventIDs []id.EventID `json:"event_ids"`
+
+ InsertionEventID id.EventID `json:"insertion_event_id"`
+ BatchEventID id.EventID `json:"batch_event_id"`
+ BaseInsertionEventID id.EventID `json:"base_insertion_event_id"`
+
+ NextBatchID id.BatchID `json:"next_batch_id"`
+}
+
+// RespCapabilities is the JSON response for https://spec.matrix.org/v1.3/client-server-api/#get_matrixclientv3capabilities
+type RespCapabilities struct {
+ RoomVersions *CapRoomVersions `json:"m.room_versions,omitempty"`
+ ChangePassword *CapBooleanTrue `json:"m.change_password,omitempty"`
+ SetDisplayname *CapBooleanTrue `json:"m.set_displayname,omitempty"`
+ SetAvatarURL *CapBooleanTrue `json:"m.set_avatar_url,omitempty"`
+ ThreePIDChanges *CapBooleanTrue `json:"m.3pid_changes,omitempty"`
+
+ Custom map[string]interface{} `json:"-"`
+}
+
+type serializableRespCapabilities RespCapabilities
+
+func (rc *RespCapabilities) UnmarshalJSON(data []byte) error {
+ res := gjson.GetBytes(data, "capabilities")
+ if !res.Exists() || !res.IsObject() {
+ return nil
+ }
+ if res.Index > 0 {
+ data = data[res.Index : res.Index+len(res.Raw)]
+ } else {
+ data = []byte(res.Raw)
+ }
+ err := json.Unmarshal(data, (*serializableRespCapabilities)(rc))
+ if err != nil {
+ return err
+ }
+ err = json.Unmarshal(data, &rc.Custom)
+ if err != nil {
+ return err
+ }
+ // Remove non-custom capabilities from the custom map so that they don't get overridden when serializing back
+ for _, field := range reflect.VisibleFields(reflect.TypeOf(rc).Elem()) {
+ jsonTag := strings.Split(field.Tag.Get("json"), ",")[0]
+ if jsonTag != "-" && jsonTag != "" {
+ delete(rc.Custom, jsonTag)
+ }
+ }
+ return nil
+}
+
+func (rc *RespCapabilities) MarshalJSON() ([]byte, error) {
+ marshalableCopy := make(map[string]interface{}, len(rc.Custom))
+ val := reflect.ValueOf(rc).Elem()
+ for _, field := range reflect.VisibleFields(val.Type()) {
+ jsonTag := strings.Split(field.Tag.Get("json"), ",")[0]
+ if jsonTag != "-" && jsonTag != "" {
+ fieldVal := val.FieldByIndex(field.Index)
+ if !fieldVal.IsNil() {
+ marshalableCopy[jsonTag] = fieldVal.Interface()
+ }
+ }
+ }
+ if rc.Custom != nil {
+ for key, value := range rc.Custom {
+ marshalableCopy[key] = value
+ }
+ }
+ var buf bytes.Buffer
+ buf.WriteString(`{"capabilities":`)
+ err := json.NewEncoder(&buf).Encode(marshalableCopy)
+ if err != nil {
+ return nil, err
+ }
+ buf.WriteByte('}')
+ return buf.Bytes(), nil
+}
+
+type CapBoolean struct {
+ Enabled bool `json:"enabled"`
+}
+
+type CapBooleanTrue CapBoolean
+
+// IsEnabled returns true if the capability is either enabled explicitly or not specified (nil)
+func (cb *CapBooleanTrue) IsEnabled() bool {
+ // Default to true when
+ return cb == nil || cb.Enabled
+}
+
+type CapBooleanFalse CapBoolean
+
+// IsEnabled returns true if the capability is enabled explicitly. If it's not specified, this returns false.
+func (cb *CapBooleanFalse) IsEnabled() bool {
+ return cb != nil && cb.Enabled
+}
+
+type CapRoomVersionStability string
+
+const (
+ CapRoomVersionStable CapRoomVersionStability = "stable"
+ CapRoomVersionUnstable CapRoomVersionStability = "unstable"
+)
+
+type CapRoomVersions struct {
+ Default string `json:"default"`
+ Available map[string]CapRoomVersionStability `json:"available"`
+}
+
+func (vers *CapRoomVersions) IsStable(version string) bool {
+ if vers == nil || vers.Available == nil {
+ val, err := strconv.Atoi(version)
+ return err == nil && val > 0
+ }
+ return vers.Available[version] == CapRoomVersionStable
+}
+
+func (vers *CapRoomVersions) IsAvailable(version string) bool {
+ if vers == nil || vers.Available == nil {
+ return false
+ }
+ _, available := vers.Available[version]
+ return available
+}
+
+// RespHierarchy is the JSON response for https://spec.matrix.org/v1.4/client-server-api/#get_matrixclientv1roomsroomidhierarchy
+type RespHierarchy struct {
+ NextBatch string `json:"next_batch,omitempty"`
+ Rooms []ChildRoomsChunk `json:"rooms"`
+}
+
+type ChildRoomsChunk struct {
+ AvatarURL id.ContentURI `json:"avatar_url,omitempty"`
+ CanonicalAlias id.RoomAlias `json:"canonical_alias,omitempty"`
+ ChildrenState []StrippedStateWithTime `json:"children_state"`
+ GuestCanJoin bool `json:"guest_can_join"`
+ JoinRule event.JoinRule `json:"join_rule,omitempty"`
+ Name string `json:"name,omitempty"`
+ NumJoinedMembers int `json:"num_joined_members"`
+ RoomID id.RoomID `json:"room_id"`
+ RoomType event.RoomType `json:"room_type"`
+ Topic string `json:"topic,omitempty"`
+ WorldReadble bool `json:"world_readable"`
+}
+
+type StrippedStateWithTime struct {
+ event.StrippedState
+ Timestamp jsontime.UnixMilli `json:"origin_server_ts"`
+}
+
+type RespAppservicePing struct {
+ DurationMS int64 `json:"duration"`
+}
+
+type RespBeeperMergeRoom RespCreateRoom
+
+type RespBeeperSplitRoom struct {
+ RoomIDs map[string]id.RoomID `json:"room_ids"`
+}
+
+type RespTimestampToEvent struct {
+ EventID id.EventID `json:"event_id"`
+ Timestamp jsontime.UnixMilli `json:"origin_server_ts"`
+}
+
+type RespRoomKeysVersionCreate struct {
+ Version string `json:"version"`
+}
+
+type RespRoomKeysVersion struct {
+ Algorithm string `json:"algorithm"`
+ AuthData json.RawMessage `json:"auth_data"`
+ Count int `json:"count"`
+ ETag string `json:"etag"`
+ Version string `json:"version"`
+}
+
+type RespRoomKeys struct {
+ Rooms map[id.RoomID]RespRoomKeysRoom `json:"rooms"`
+}
+
+type RespRoomKeysRoom struct {
+ Sessions map[id.SessionID]RespRoomKeysSession `json:"sessions"`
+}
+
+type RespRoomKeysSession struct {
+ FirstMessageIndex int `json:"first_message_index"`
+ ForwardedCount int `json:"forwarded_count"`
+ IsVerified bool `json:"is_verified"`
+ SessionData json.RawMessage `json:"session_data"`
+}
+
+type RespRoomKeysUpdate struct {
+ Count int `json:"count"`
+ ETag string `json:"etag"`
+}
diff --git a/vendor/maunium.net/go/mautrix/room.go b/vendor/maunium.net/go/mautrix/room.go
new file mode 100644
index 00000000..c3ddb7e6
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/room.go
@@ -0,0 +1,54 @@
+package mautrix
+
+import (
+ "maunium.net/go/mautrix/event"
+ "maunium.net/go/mautrix/id"
+)
+
+type RoomStateMap = map[event.Type]map[string]*event.Event
+
+// Room represents a single Matrix room.
+type Room struct {
+ ID id.RoomID
+ State RoomStateMap
+}
+
+// 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(evt *event.Event) {
+ _, exists := room.State[evt.Type]
+ if !exists {
+ room.State[evt.Type] = make(map[string]*event.Event)
+ }
+ room.State[evt.Type][*evt.StateKey] = evt
+}
+
+// GetStateEvent returns the state event for the given type/state_key combo, or nil.
+func (room Room) GetStateEvent(eventType event.Type, stateKey string) *event.Event {
+ stateEventMap, _ := room.State[eventType]
+ evt, _ := stateEventMap[stateKey]
+ return evt
+}
+
+// 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 id.UserID) event.Membership {
+ state := event.MembershipLeave
+ evt := room.GetStateEvent(event.StateMember, string(userID))
+ if evt != nil {
+ membership, ok := evt.Content.Raw["membership"].(string)
+ if ok {
+ state = event.Membership(membership)
+ }
+ }
+ return state
+}
+
+// NewRoom creates a new Room with the given ID
+func NewRoom(roomID id.RoomID) *Room {
+ // Init the State map and return a pointer to the Room
+ return &Room{
+ ID: roomID,
+ State: make(RoomStateMap),
+ }
+}
diff --git a/vendor/maunium.net/go/mautrix/statestore.go b/vendor/maunium.net/go/mautrix/statestore.go
new file mode 100644
index 00000000..bfe38cc9
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/statestore.go
@@ -0,0 +1,221 @@
+// Copyright (c) 2023 Tulir Asokan
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package mautrix
+
+import (
+ "sync"
+
+ "maunium.net/go/mautrix/event"
+ "maunium.net/go/mautrix/id"
+)
+
+// StateStore is an interface for storing basic room state information.
+type StateStore interface {
+ IsInRoom(roomID id.RoomID, userID id.UserID) bool
+ IsInvited(roomID id.RoomID, userID id.UserID) bool
+ IsMembership(roomID id.RoomID, userID id.UserID, allowedMemberships ...event.Membership) bool
+ GetMember(roomID id.RoomID, userID id.UserID) *event.MemberEventContent
+ TryGetMember(roomID id.RoomID, userID id.UserID) (*event.MemberEventContent, bool)
+ SetMembership(roomID id.RoomID, userID id.UserID, membership event.Membership)
+ SetMember(roomID id.RoomID, userID id.UserID, member *event.MemberEventContent)
+
+ SetPowerLevels(roomID id.RoomID, levels *event.PowerLevelsEventContent)
+ GetPowerLevels(roomID id.RoomID) *event.PowerLevelsEventContent
+
+ SetEncryptionEvent(roomID id.RoomID, content *event.EncryptionEventContent)
+ IsEncrypted(roomID id.RoomID) bool
+}
+
+func UpdateStateStore(store StateStore, evt *event.Event) {
+ if store == nil || evt == nil || evt.StateKey == nil {
+ return
+ }
+ // We only care about events without a state key (power levels, encryption) or member events with state key
+ if evt.Type != event.StateMember && evt.GetStateKey() != "" {
+ return
+ }
+ switch content := evt.Content.Parsed.(type) {
+ case *event.MemberEventContent:
+ store.SetMember(evt.RoomID, id.UserID(evt.GetStateKey()), content)
+ case *event.PowerLevelsEventContent:
+ store.SetPowerLevels(evt.RoomID, content)
+ case *event.EncryptionEventContent:
+ store.SetEncryptionEvent(evt.RoomID, content)
+ }
+}
+
+// StateStoreSyncHandler can be added as an event handler in the syncer to update the state store automatically.
+//
+// client.Syncer.(mautrix.ExtensibleSyncer).OnEvent(client.StateStoreSyncHandler)
+//
+// DefaultSyncer.ParseEventContent must also be true for this to work (which it is by default).
+func (cli *Client) StateStoreSyncHandler(_ EventSource, evt *event.Event) {
+ UpdateStateStore(cli.StateStore, evt)
+}
+
+type MemoryStateStore struct {
+ Registrations map[id.UserID]bool `json:"registrations"`
+ Members map[id.RoomID]map[id.UserID]*event.MemberEventContent `json:"memberships"`
+ PowerLevels map[id.RoomID]*event.PowerLevelsEventContent `json:"power_levels"`
+ Encryption map[id.RoomID]*event.EncryptionEventContent `json:"encryption"`
+
+ registrationsLock sync.RWMutex
+ membersLock sync.RWMutex
+ powerLevelsLock sync.RWMutex
+ encryptionLock sync.RWMutex
+}
+
+func NewMemoryStateStore() StateStore {
+ return &MemoryStateStore{
+ Registrations: make(map[id.UserID]bool),
+ Members: make(map[id.RoomID]map[id.UserID]*event.MemberEventContent),
+ PowerLevels: make(map[id.RoomID]*event.PowerLevelsEventContent),
+ }
+}
+
+func (store *MemoryStateStore) IsRegistered(userID id.UserID) bool {
+ store.registrationsLock.RLock()
+ defer store.registrationsLock.RUnlock()
+ registered, ok := store.Registrations[userID]
+ return ok && registered
+}
+
+func (store *MemoryStateStore) MarkRegistered(userID id.UserID) {
+ store.registrationsLock.Lock()
+ defer store.registrationsLock.Unlock()
+ store.Registrations[userID] = true
+}
+
+func (store *MemoryStateStore) GetRoomMembers(roomID id.RoomID) map[id.UserID]*event.MemberEventContent {
+ store.membersLock.RLock()
+ members, ok := store.Members[roomID]
+ store.membersLock.RUnlock()
+ if !ok {
+ members = make(map[id.UserID]*event.MemberEventContent)
+ store.membersLock.Lock()
+ store.Members[roomID] = members
+ store.membersLock.Unlock()
+ }
+ return members
+}
+
+func (store *MemoryStateStore) GetMembership(roomID id.RoomID, userID id.UserID) event.Membership {
+ return store.GetMember(roomID, userID).Membership
+}
+
+func (store *MemoryStateStore) GetMember(roomID id.RoomID, userID id.UserID) *event.MemberEventContent {
+ member, ok := store.TryGetMember(roomID, userID)
+ if !ok {
+ member = &event.MemberEventContent{Membership: event.MembershipLeave}
+ }
+ return member
+}
+
+func (store *MemoryStateStore) TryGetMember(roomID id.RoomID, userID id.UserID) (member *event.MemberEventContent, ok bool) {
+ store.membersLock.RLock()
+ defer store.membersLock.RUnlock()
+ members, membersOk := store.Members[roomID]
+ if !membersOk {
+ return
+ }
+ member, ok = members[userID]
+ return
+}
+
+func (store *MemoryStateStore) IsInRoom(roomID id.RoomID, userID id.UserID) bool {
+ return store.IsMembership(roomID, userID, "join")
+}
+
+func (store *MemoryStateStore) IsInvited(roomID id.RoomID, userID id.UserID) bool {
+ return store.IsMembership(roomID, userID, "join", "invite")
+}
+
+func (store *MemoryStateStore) IsMembership(roomID id.RoomID, userID id.UserID, allowedMemberships ...event.Membership) bool {
+ membership := store.GetMembership(roomID, userID)
+ for _, allowedMembership := range allowedMemberships {
+ if allowedMembership == membership {
+ return true
+ }
+ }
+ return false
+}
+
+func (store *MemoryStateStore) SetMembership(roomID id.RoomID, userID id.UserID, membership event.Membership) {
+ store.membersLock.Lock()
+ members, ok := store.Members[roomID]
+ if !ok {
+ members = map[id.UserID]*event.MemberEventContent{
+ userID: {Membership: membership},
+ }
+ } else {
+ member, ok := members[userID]
+ if !ok {
+ members[userID] = &event.MemberEventContent{Membership: membership}
+ } else {
+ member.Membership = membership
+ members[userID] = member
+ }
+ }
+ store.Members[roomID] = members
+ store.membersLock.Unlock()
+}
+
+func (store *MemoryStateStore) SetMember(roomID id.RoomID, userID id.UserID, member *event.MemberEventContent) {
+ store.membersLock.Lock()
+ members, ok := store.Members[roomID]
+ if !ok {
+ members = map[id.UserID]*event.MemberEventContent{
+ userID: member,
+ }
+ } else {
+ members[userID] = member
+ }
+ store.Members[roomID] = members
+ store.membersLock.Unlock()
+}
+
+func (store *MemoryStateStore) SetPowerLevels(roomID id.RoomID, levels *event.PowerLevelsEventContent) {
+ store.powerLevelsLock.Lock()
+ store.PowerLevels[roomID] = levels
+ store.powerLevelsLock.Unlock()
+}
+
+func (store *MemoryStateStore) GetPowerLevels(roomID id.RoomID) (levels *event.PowerLevelsEventContent) {
+ store.powerLevelsLock.RLock()
+ levels = store.PowerLevels[roomID]
+ store.powerLevelsLock.RUnlock()
+ return
+}
+
+func (store *MemoryStateStore) GetPowerLevel(roomID id.RoomID, userID id.UserID) int {
+ return store.GetPowerLevels(roomID).GetUserLevel(userID)
+}
+
+func (store *MemoryStateStore) GetPowerLevelRequirement(roomID id.RoomID, eventType event.Type) int {
+ return store.GetPowerLevels(roomID).GetEventLevel(eventType)
+}
+
+func (store *MemoryStateStore) HasPowerLevel(roomID id.RoomID, userID id.UserID, eventType event.Type) bool {
+ return store.GetPowerLevel(roomID, userID) >= store.GetPowerLevelRequirement(roomID, eventType)
+}
+
+func (store *MemoryStateStore) SetEncryptionEvent(roomID id.RoomID, content *event.EncryptionEventContent) {
+ store.encryptionLock.Lock()
+ store.Encryption[roomID] = content
+ store.encryptionLock.Unlock()
+}
+
+func (store *MemoryStateStore) GetEncryptionEvent(roomID id.RoomID) *event.EncryptionEventContent {
+ store.encryptionLock.RLock()
+ defer store.encryptionLock.RUnlock()
+ return store.Encryption[roomID]
+}
+
+func (store *MemoryStateStore) IsEncrypted(roomID id.RoomID) bool {
+ cfg := store.GetEncryptionEvent(roomID)
+ return cfg != nil && cfg.Algorithm == id.AlgorithmMegolmV1
+}
diff --git a/vendor/maunium.net/go/mautrix/sync.go b/vendor/maunium.net/go/mautrix/sync.go
new file mode 100644
index 00000000..6d3a1756
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/sync.go
@@ -0,0 +1,310 @@
+// Copyright (c) 2020 Tulir Asokan
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package mautrix
+
+import (
+ "errors"
+ "fmt"
+ "runtime/debug"
+ "time"
+
+ "maunium.net/go/mautrix/event"
+ "maunium.net/go/mautrix/id"
+)
+
+// EventSource represents the part of the sync response that an event came from.
+type EventSource int
+
+const (
+ EventSourcePresence EventSource = 1 << iota
+ EventSourceJoin
+ EventSourceInvite
+ EventSourceLeave
+ EventSourceAccountData
+ EventSourceTimeline
+ EventSourceState
+ EventSourceEphemeral
+ EventSourceToDevice
+ EventSourceDecrypted
+)
+
+const primaryTypes = EventSourcePresence | EventSourceAccountData | EventSourceToDevice | EventSourceTimeline | EventSourceState
+const roomSections = EventSourceJoin | EventSourceInvite | EventSourceLeave
+const roomableTypes = EventSourceAccountData | EventSourceTimeline | EventSourceState
+const encryptableTypes = roomableTypes | EventSourceToDevice
+
+func (es EventSource) String() string {
+ var typeName string
+ switch es & primaryTypes {
+ case EventSourcePresence:
+ typeName = "presence"
+ case EventSourceAccountData:
+ typeName = "account data"
+ case EventSourceToDevice:
+ typeName = "to-device"
+ case EventSourceTimeline:
+ typeName = "timeline"
+ case EventSourceState:
+ typeName = "state"
+ default:
+ return fmt.Sprintf("unknown (%d)", es)
+ }
+ if es&roomableTypes != 0 {
+ switch es & roomSections {
+ case EventSourceJoin:
+ typeName = "joined room " + typeName
+ case EventSourceInvite:
+ typeName = "invited room " + typeName
+ case EventSourceLeave:
+ typeName = "left room " + typeName
+ default:
+ return fmt.Sprintf("unknown (%d)", es)
+ }
+ es &^= roomableTypes
+ }
+ if es&encryptableTypes != 0 && es&EventSourceDecrypted != 0 {
+ typeName += " (decrypted)"
+ es &^= EventSourceDecrypted
+ }
+ es &^= primaryTypes
+ if es != 0 {
+ return fmt.Sprintf("unknown (%d)", es)
+ }
+ return typeName
+}
+
+// EventHandler handles a single event from a sync response.
+type EventHandler func(source EventSource, evt *event.Event)
+
+// SyncHandler handles a whole sync response. If the return value is false, handling will be stopped completely.
+type SyncHandler func(resp *RespSync, since string) bool
+
+// Syncer is an interface that must be satisfied in order to do /sync requests on a client.
+type Syncer interface {
+ // ProcessResponse processes 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 id.UserID) *Filter
+}
+
+type ExtensibleSyncer interface {
+ OnSync(callback SyncHandler)
+ OnEvent(callback EventHandler)
+ OnEventType(eventType event.Type, callback EventHandler)
+}
+
+type DispatchableSyncer interface {
+ Dispatch(source EventSource, evt *event.Event)
+}
+
+// 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 {
+ // syncListeners want the whole sync response, e.g. the crypto machine
+ syncListeners []SyncHandler
+ // globalListeners want all events
+ globalListeners []EventHandler
+ // listeners want a specific event type
+ listeners map[event.Type][]EventHandler
+ // ParseEventContent determines whether or not event content should be parsed before passing to handlers.
+ ParseEventContent bool
+ // ParseErrorHandler is called when event.Content.ParseRaw returns an error.
+ // If it returns false, the event will not be forwarded to listeners.
+ ParseErrorHandler func(evt *event.Event, err error) bool
+ // FilterJSON is used when the client starts syncing and doesn't get an existing filter ID from SyncStore's LoadFilterID.
+ FilterJSON *Filter
+}
+
+var _ Syncer = (*DefaultSyncer)(nil)
+var _ ExtensibleSyncer = (*DefaultSyncer)(nil)
+
+// NewDefaultSyncer returns an instantiated DefaultSyncer
+func NewDefaultSyncer() *DefaultSyncer {
+ return &DefaultSyncer{
+ listeners: make(map[event.Type][]EventHandler),
+ syncListeners: []SyncHandler{},
+ globalListeners: []EventHandler{},
+ ParseEventContent: true,
+ ParseErrorHandler: func(evt *event.Event, err error) bool {
+ return false
+ },
+ }
+}
+
+// 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) {
+ defer func() {
+ if r := recover(); r != nil {
+ err = fmt.Errorf("ProcessResponse panicked! since=%s panic=%s\n%s", since, r, debug.Stack())
+ }
+ }()
+
+ for _, listener := range s.syncListeners {
+ if !listener(res, since) {
+ return
+ }
+ }
+
+ s.processSyncEvents("", res.ToDevice.Events, EventSourceToDevice)
+ s.processSyncEvents("", res.Presence.Events, EventSourcePresence)
+ s.processSyncEvents("", res.AccountData.Events, EventSourceAccountData)
+
+ for roomID, roomData := range res.Rooms.Join {
+ s.processSyncEvents(roomID, roomData.State.Events, EventSourceJoin|EventSourceState)
+ s.processSyncEvents(roomID, roomData.Timeline.Events, EventSourceJoin|EventSourceTimeline)
+ s.processSyncEvents(roomID, roomData.Ephemeral.Events, EventSourceJoin|EventSourceEphemeral)
+ s.processSyncEvents(roomID, roomData.AccountData.Events, EventSourceJoin|EventSourceAccountData)
+ }
+ for roomID, roomData := range res.Rooms.Invite {
+ s.processSyncEvents(roomID, roomData.State.Events, EventSourceInvite|EventSourceState)
+ }
+ for roomID, roomData := range res.Rooms.Leave {
+ s.processSyncEvents(roomID, roomData.State.Events, EventSourceLeave|EventSourceState)
+ s.processSyncEvents(roomID, roomData.Timeline.Events, EventSourceLeave|EventSourceTimeline)
+ }
+ return
+}
+
+func (s *DefaultSyncer) processSyncEvents(roomID id.RoomID, events []*event.Event, source EventSource) {
+ for _, evt := range events {
+ s.processSyncEvent(roomID, evt, source)
+ }
+}
+
+func (s *DefaultSyncer) processSyncEvent(roomID id.RoomID, evt *event.Event, source EventSource) {
+ evt.RoomID = roomID
+
+ // Ensure the type class is correct. It's safe to mutate the class since the event type is not a pointer.
+ // Listeners are keyed by type structs, which means only the correct class will pass.
+ switch {
+ case evt.StateKey != nil:
+ evt.Type.Class = event.StateEventType
+ case source == EventSourcePresence, source&EventSourceEphemeral != 0:
+ evt.Type.Class = event.EphemeralEventType
+ case source&EventSourceAccountData != 0:
+ evt.Type.Class = event.AccountDataEventType
+ case source == EventSourceToDevice:
+ evt.Type.Class = event.ToDeviceEventType
+ default:
+ evt.Type.Class = event.MessageEventType
+ }
+
+ if s.ParseEventContent {
+ err := evt.Content.ParseRaw(evt.Type)
+ if err != nil && !s.ParseErrorHandler(evt, err) {
+ return
+ }
+ }
+
+ s.Dispatch(source, evt)
+}
+
+func (s *DefaultSyncer) Dispatch(source EventSource, evt *event.Event) {
+ for _, fn := range s.globalListeners {
+ fn(source, evt)
+ }
+ listeners, exists := s.listeners[evt.Type]
+ if exists {
+ for _, fn := range listeners {
+ fn(source, evt)
+ }
+ }
+}
+
+// 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 event.Type, callback EventHandler) {
+ _, exists := s.listeners[eventType]
+ if !exists {
+ s.listeners[eventType] = []EventHandler{}
+ }
+ s.listeners[eventType] = append(s.listeners[eventType], callback)
+}
+
+func (s *DefaultSyncer) OnSync(callback SyncHandler) {
+ s.syncListeners = append(s.syncListeners, callback)
+}
+
+func (s *DefaultSyncer) OnEvent(callback EventHandler) {
+ s.globalListeners = append(s.globalListeners, callback)
+}
+
+// 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) {
+ if errors.Is(err, MUnknownToken) {
+ return 0, err
+ }
+ return 10 * time.Second, nil
+}
+
+var defaultFilter = Filter{
+ Room: RoomFilter{
+ Timeline: FilterPart{
+ Limit: 50,
+ },
+ },
+}
+
+// GetFilterJSON returns a filter with a timeline limit of 50.
+func (s *DefaultSyncer) GetFilterJSON(userID id.UserID) *Filter {
+ if s.FilterJSON == nil {
+ defaultFilterCopy := defaultFilter
+ s.FilterJSON = &defaultFilterCopy
+ }
+ return s.FilterJSON
+}
+
+// OldEventIgnorer is an utility struct for bots to ignore events from before the bot joined the room.
+//
+// Create a struct and call Register with your DefaultSyncer to register the sync handler, e.g.:
+//
+// (&OldEventIgnorer{UserID: cli.UserID}).Register(cli.Syncer.(mautrix.ExtensibleSyncer))
+type OldEventIgnorer struct {
+ UserID id.UserID
+}
+
+func (oei *OldEventIgnorer) Register(syncer ExtensibleSyncer) {
+ syncer.OnSync(oei.DontProcessOldEvents)
+}
+
+// DontProcessOldEvents returns true if a sync response should be processed. May modify the response to remove
+// stuff that shouldn't be processed.
+func (oei *OldEventIgnorer) DontProcessOldEvents(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-- {
+ evt := roomData.Timeline.Events[i]
+ if evt.Type == event.StateMember && evt.GetStateKey() == string(oei.UserID) {
+ membership, _ := evt.Content.Raw["membership"].(string)
+ if membership == "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
+}
diff --git a/vendor/maunium.net/go/mautrix/syncstore.go b/vendor/maunium.net/go/mautrix/syncstore.go
new file mode 100644
index 00000000..a3611e5e
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/syncstore.go
@@ -0,0 +1,149 @@
+package mautrix
+
+import (
+ "maunium.net/go/mautrix/id"
+)
+
+// SyncStore 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 "MemorySyncStore" which just keeps data around in-memory which is lost on
+// restarts.
+type SyncStore interface {
+ SaveFilterID(userID id.UserID, filterID string)
+ LoadFilterID(userID id.UserID) string
+ SaveNextBatch(userID id.UserID, nextBatchToken string)
+ LoadNextBatch(userID id.UserID) string
+}
+
+// Deprecated: renamed to SyncStore
+type Storer = SyncStore
+
+// MemorySyncStore 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 MemorySyncStore struct {
+ Filters map[id.UserID]string
+ NextBatch map[id.UserID]string
+}
+
+// SaveFilterID to memory.
+func (s *MemorySyncStore) SaveFilterID(userID id.UserID, filterID string) {
+ s.Filters[userID] = filterID
+}
+
+// LoadFilterID from memory.
+func (s *MemorySyncStore) LoadFilterID(userID id.UserID) string {
+ return s.Filters[userID]
+}
+
+// SaveNextBatch to memory.
+func (s *MemorySyncStore) SaveNextBatch(userID id.UserID, nextBatchToken string) {
+ s.NextBatch[userID] = nextBatchToken
+}
+
+// LoadNextBatch from memory.
+func (s *MemorySyncStore) LoadNextBatch(userID id.UserID) string {
+ return s.NextBatch[userID]
+}
+
+// NewMemorySyncStore constructs a new MemorySyncStore.
+func NewMemorySyncStore() *MemorySyncStore {
+ return &MemorySyncStore{
+ Filters: make(map[id.UserID]string),
+ NextBatch: make(map[id.UserID]string),
+ }
+}
+
+// AccountDataStore uses account data to store the next batch token, and stores the filter ID in memory
+// (as filters can be safely recreated every startup).
+type AccountDataStore struct {
+ FilterID string
+ EventType string
+ client *Client
+}
+
+type accountData struct {
+ NextBatch string `json:"next_batch"`
+}
+
+func (s *AccountDataStore) SaveFilterID(userID id.UserID, filterID string) {
+ if userID.String() != s.client.UserID.String() {
+ panic("AccountDataStore must only be used with a single account")
+ }
+ s.FilterID = filterID
+}
+
+func (s *AccountDataStore) LoadFilterID(userID id.UserID) string {
+ if userID.String() != s.client.UserID.String() {
+ panic("AccountDataStore must only be used with a single account")
+ }
+ return s.FilterID
+}
+
+func (s *AccountDataStore) SaveNextBatch(userID id.UserID, nextBatchToken string) {
+ if userID.String() != s.client.UserID.String() {
+ panic("AccountDataStore must only be used with a single account")
+ }
+
+ data := accountData{
+ NextBatch: nextBatchToken,
+ }
+
+ err := s.client.SetAccountData(s.EventType, data)
+ if err != nil {
+ s.client.Log.Warn().Err(err).Msg("Failed to save next batch token to account data")
+ }
+}
+
+func (s *AccountDataStore) LoadNextBatch(userID id.UserID) string {
+ if userID.String() != s.client.UserID.String() {
+ panic("AccountDataStore must only be used with a single account")
+ }
+
+ data := &accountData{}
+
+ err := s.client.GetAccountData(s.EventType, data)
+ if err != nil {
+ s.client.Log.Warn().Err(err).Msg("Failed to load next batch token from account data")
+ return ""
+ }
+
+ return data.NextBatch
+}
+
+// NewAccountDataStore returns a new AccountDataStore, which stores
+// the next_batch token as a custom event in account data in the
+// homeserver.
+//
+// AccountDataStore is only appropriate for bots, not appservices.
+//
+// The event type should be a reversed DNS name like tld.domain.sub.internal and
+// must be unique for a client. The data stored in it is considered internal
+// and must not be modified through outside means. You should also add a filter
+// for account data changes of this event type, to avoid ending up in a sync
+// loop:
+//
+// filter := mautrix.Filter{
+// AccountData: mautrix.FilterPart{
+// Limit: 20,
+// NotTypes: []event.Type{
+// event.NewEventType(eventType),
+// },
+// },
+// }
+// // If you use a custom Syncer, set the filter there, not like this
+// client.Syncer.(*mautrix.DefaultSyncer).FilterJSON = &filter
+// client.Store = mautrix.NewAccountDataStore("com.example.mybot.store", client)
+// go func() {
+// err := client.Sync()
+// // don't forget to check err
+// }()
+func NewAccountDataStore(eventType string, client *Client) *AccountDataStore {
+ return &AccountDataStore{
+ EventType: eventType,
+ client: client,
+ }
+}
diff --git a/vendor/maunium.net/go/mautrix/url.go b/vendor/maunium.net/go/mautrix/url.go
new file mode 100644
index 00000000..5ea03f1d
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/url.go
@@ -0,0 +1,106 @@
+// Copyright (c) 2022 Tulir Asokan
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package mautrix
+
+import (
+ "fmt"
+ "net/url"
+ "strconv"
+ "strings"
+)
+
+func ParseAndNormalizeBaseURL(homeserverURL string) (*url.URL, error) {
+ hsURL, err := url.Parse(homeserverURL)
+ if err != nil {
+ return nil, err
+ }
+ if hsURL.Scheme == "" {
+ hsURL.Scheme = "https"
+ fixedURL := hsURL.String()
+ hsURL, err = url.Parse(fixedURL)
+ if err != nil {
+ return nil, fmt.Errorf("failed to parse fixed URL '%s': %v", fixedURL, err)
+ }
+ }
+ hsURL.RawPath = hsURL.EscapedPath()
+ return hsURL, nil
+}
+
+// BuildURL builds a URL with the given path parts
+func BuildURL(baseURL *url.URL, path ...interface{}) *url.URL {
+ createdURL := *baseURL
+ rawParts := make([]string, len(path)+1)
+ rawParts[0] = strings.TrimSuffix(createdURL.RawPath, "/")
+ parts := make([]string, len(path)+1)
+ parts[0] = strings.TrimSuffix(createdURL.Path, "/")
+ for i, part := range path {
+ switch casted := part.(type) {
+ case string:
+ parts[i+1] = casted
+ case int:
+ parts[i+1] = strconv.Itoa(casted)
+ case fmt.Stringer:
+ parts[i+1] = casted.String()
+ default:
+ parts[i+1] = fmt.Sprint(casted)
+ }
+ rawParts[i+1] = url.PathEscape(parts[i+1])
+ }
+ createdURL.Path = strings.Join(parts, "/")
+ createdURL.RawPath = strings.Join(rawParts, "/")
+ return &createdURL
+}
+
+// BuildURL builds a URL with the Client's homeserver and appservice user ID set already.
+func (cli *Client) BuildURL(urlPath PrefixableURLPath) string {
+ return cli.BuildURLWithQuery(urlPath, nil)
+}
+
+// BuildClientURL builds a URL with the Client's homeserver and appservice user ID set already.
+// This method also automatically prepends the client API prefix (/_matrix/client).
+func (cli *Client) BuildClientURL(urlPath ...interface{}) string {
+ return cli.BuildURLWithQuery(ClientURLPath(urlPath), nil)
+}
+
+type PrefixableURLPath interface {
+ FullPath() []interface{}
+}
+
+type BaseURLPath []interface{}
+
+func (bup BaseURLPath) FullPath() []interface{} {
+ return bup
+}
+
+type ClientURLPath []interface{}
+
+func (cup ClientURLPath) FullPath() []interface{} {
+ return append([]interface{}{"_matrix", "client"}, []interface{}(cup)...)
+}
+
+type MediaURLPath []interface{}
+
+func (mup MediaURLPath) FullPath() []interface{} {
+ return append([]interface{}{"_matrix", "media"}, []interface{}(mup)...)
+}
+
+// BuildURLWithQuery builds a URL with query parameters in addition to the Client's homeserver
+// and appservice user ID set already.
+func (cli *Client) BuildURLWithQuery(urlPath PrefixableURLPath, urlQuery map[string]string) string {
+ hsURL := *BuildURL(cli.HomeserverURL, urlPath.FullPath()...)
+ query := hsURL.Query()
+ if cli.SetAppServiceUserID {
+ query.Set("user_id", string(cli.UserID))
+ }
+ if urlQuery != nil {
+ for k, v := range urlQuery {
+ query.Set(k, v)
+ }
+ }
+ hsURL.RawQuery = query.Encode()
+ return hsURL.String()
+}
diff --git a/vendor/maunium.net/go/mautrix/util/base58/README.md b/vendor/maunium.net/go/mautrix/util/base58/README.md
new file mode 100644
index 00000000..50954507
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/util/base58/README.md
@@ -0,0 +1,9 @@
+base58
+==========
+
+This is a copy of <https://github.com/btcsuite/btcd/tree/master/btcutil/base58>.
+
+## License
+
+Package base58 is licensed under the [copyfree](http://copyfree.org) ISC
+License.
diff --git a/vendor/maunium.net/go/mautrix/util/base58/alphabet.go b/vendor/maunium.net/go/mautrix/util/base58/alphabet.go
new file mode 100644
index 00000000..6bb39fef
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/util/base58/alphabet.go
@@ -0,0 +1,49 @@
+// Copyright (c) 2015 The btcsuite developers
+// Use of this source code is governed by an ISC
+// license that can be found in the LICENSE file.
+
+// AUTOGENERATED by genalphabet.go; do not edit.
+
+package base58
+
+const (
+ // alphabet is the modified base58 alphabet used by Bitcoin.
+ alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
+
+ alphabetIdx0 = '1'
+)
+
+var b58 = [256]byte{
+ 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 0, 1, 2, 3, 4, 5, 6,
+ 7, 8, 255, 255, 255, 255, 255, 255,
+ 255, 9, 10, 11, 12, 13, 14, 15,
+ 16, 255, 17, 18, 19, 20, 21, 255,
+ 22, 23, 24, 25, 26, 27, 28, 29,
+ 30, 31, 32, 255, 255, 255, 255, 255,
+ 255, 33, 34, 35, 36, 37, 38, 39,
+ 40, 41, 42, 43, 255, 44, 45, 46,
+ 47, 48, 49, 50, 51, 52, 53, 54,
+ 55, 56, 57, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255,
+}
diff --git a/vendor/maunium.net/go/mautrix/util/base58/base58.go b/vendor/maunium.net/go/mautrix/util/base58/base58.go
new file mode 100644
index 00000000..8ee59567
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/util/base58/base58.go
@@ -0,0 +1,138 @@
+// Copyright (c) 2013-2015 The btcsuite developers
+// Use of this source code is governed by an ISC
+// license that can be found in the LICENSE file.
+
+package base58
+
+import (
+ "math/big"
+)
+
+//go:generate go run genalphabet.go
+
+var bigRadix = [...]*big.Int{
+ big.NewInt(0),
+ big.NewInt(58),
+ big.NewInt(58 * 58),
+ big.NewInt(58 * 58 * 58),
+ big.NewInt(58 * 58 * 58 * 58),
+ big.NewInt(58 * 58 * 58 * 58 * 58),
+ big.NewInt(58 * 58 * 58 * 58 * 58 * 58),
+ big.NewInt(58 * 58 * 58 * 58 * 58 * 58 * 58),
+ big.NewInt(58 * 58 * 58 * 58 * 58 * 58 * 58 * 58),
+ big.NewInt(58 * 58 * 58 * 58 * 58 * 58 * 58 * 58 * 58),
+ bigRadix10,
+}
+
+var bigRadix10 = big.NewInt(58 * 58 * 58 * 58 * 58 * 58 * 58 * 58 * 58 * 58) // 58^10
+
+// Decode decodes a modified base58 string to a byte slice.
+func Decode(b string) []byte {
+ answer := big.NewInt(0)
+ scratch := new(big.Int)
+
+ // Calculating with big.Int is slow for each iteration.
+ // x += b58[b[i]] * j
+ // j *= 58
+ //
+ // Instead we can try to do as much calculations on int64.
+ // We can represent a 10 digit base58 number using an int64.
+ //
+ // Hence we'll try to convert 10, base58 digits at a time.
+ // The rough idea is to calculate `t`, such that:
+ //
+ // t := b58[b[i+9]] * 58^9 ... + b58[b[i+1]] * 58^1 + b58[b[i]] * 58^0
+ // x *= 58^10
+ // x += t
+ //
+ // Of course, in addition, we'll need to handle boundary condition when `b` is not multiple of 58^10.
+ // In that case we'll use the bigRadix[n] lookup for the appropriate power.
+ for t := b; len(t) > 0; {
+ n := len(t)
+ if n > 10 {
+ n = 10
+ }
+
+ total := uint64(0)
+ for _, v := range t[:n] {
+ tmp := b58[v]
+ if tmp == 255 {
+ return []byte("")
+ }
+ total = total*58 + uint64(tmp)
+ }
+
+ answer.Mul(answer, bigRadix[n])
+ scratch.SetUint64(total)
+ answer.Add(answer, scratch)
+
+ t = t[n:]
+ }
+
+ tmpval := answer.Bytes()
+
+ var numZeros int
+ for numZeros = 0; numZeros < len(b); numZeros++ {
+ if b[numZeros] != alphabetIdx0 {
+ break
+ }
+ }
+ flen := numZeros + len(tmpval)
+ val := make([]byte, flen)
+ copy(val[numZeros:], tmpval)
+
+ return val
+}
+
+// Encode encodes a byte slice to a modified base58 string.
+func Encode(b []byte) string {
+ x := new(big.Int)
+ x.SetBytes(b)
+
+ // maximum length of output is log58(2^(8*len(b))) == len(b) * 8 / log(58)
+ maxlen := int(float64(len(b))*1.365658237309761) + 1
+ answer := make([]byte, 0, maxlen)
+ mod := new(big.Int)
+ for x.Sign() > 0 {
+ // Calculating with big.Int is slow for each iteration.
+ // x, mod = x / 58, x % 58
+ //
+ // Instead we can try to do as much calculations on int64.
+ // x, mod = x / 58^10, x % 58^10
+ //
+ // Which will give us mod, which is 10 digit base58 number.
+ // We'll loop that 10 times to convert to the answer.
+
+ x.DivMod(x, bigRadix10, mod)
+ if x.Sign() == 0 {
+ // When x = 0, we need to ensure we don't add any extra zeros.
+ m := mod.Int64()
+ for m > 0 {
+ answer = append(answer, alphabet[m%58])
+ m /= 58
+ }
+ } else {
+ m := mod.Int64()
+ for i := 0; i < 10; i++ {
+ answer = append(answer, alphabet[m%58])
+ m /= 58
+ }
+ }
+ }
+
+ // leading zero bytes
+ for _, i := range b {
+ if i != 0 {
+ break
+ }
+ answer = append(answer, alphabetIdx0)
+ }
+
+ // reverse
+ alen := len(answer)
+ for i := 0; i < alen/2; i++ {
+ answer[i], answer[alen-1-i] = answer[alen-1-i], answer[i]
+ }
+
+ return string(answer)
+}
diff --git a/vendor/maunium.net/go/mautrix/util/base58/base58check.go b/vendor/maunium.net/go/mautrix/util/base58/base58check.go
new file mode 100644
index 00000000..402c3233
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/util/base58/base58check.go
@@ -0,0 +1,52 @@
+// Copyright (c) 2013-2014 The btcsuite developers
+// Use of this source code is governed by an ISC
+// license that can be found in the LICENSE file.
+
+package base58
+
+import (
+ "crypto/sha256"
+ "errors"
+)
+
+// ErrChecksum indicates that the checksum of a check-encoded string does not verify against
+// the checksum.
+var ErrChecksum = errors.New("checksum error")
+
+// ErrInvalidFormat indicates that the check-encoded string has an invalid format.
+var ErrInvalidFormat = errors.New("invalid format: version and/or checksum bytes missing")
+
+// checksum: first four bytes of sha256^2
+func checksum(input []byte) (cksum [4]byte) {
+ h := sha256.Sum256(input)
+ h2 := sha256.Sum256(h[:])
+ copy(cksum[:], h2[:4])
+ return
+}
+
+// CheckEncode prepends a version byte and appends a four byte checksum.
+func CheckEncode(input []byte, version byte) string {
+ b := make([]byte, 0, 1+len(input)+4)
+ b = append(b, version)
+ b = append(b, input...)
+ cksum := checksum(b)
+ b = append(b, cksum[:]...)
+ return Encode(b)
+}
+
+// CheckDecode decodes a string that was encoded with CheckEncode and verifies the checksum.
+func CheckDecode(input string) (result []byte, version byte, err error) {
+ decoded := Decode(input)
+ if len(decoded) < 5 {
+ return nil, 0, ErrInvalidFormat
+ }
+ version = decoded[0]
+ var cksum [4]byte
+ copy(cksum[:], decoded[len(decoded)-4:])
+ if checksum(decoded[:len(decoded)-4]) != cksum {
+ return nil, 0, ErrChecksum
+ }
+ payload := decoded[1 : len(decoded)-4]
+ result = append(result, payload...)
+ return
+}
diff --git a/vendor/maunium.net/go/mautrix/util/base58/doc.go b/vendor/maunium.net/go/mautrix/util/base58/doc.go
new file mode 100644
index 00000000..d657f050
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/util/base58/doc.go
@@ -0,0 +1,29 @@
+// Copyright (c) 2014 The btcsuite developers
+// Use of this source code is governed by an ISC
+// license that can be found in the LICENSE file.
+
+/*
+Package base58 provides an API for working with modified base58 and Base58Check
+encodings.
+
+# Modified Base58 Encoding
+
+Standard base58 encoding is similar to standard base64 encoding except, as the
+name implies, it uses a 58 character alphabet which results in an alphanumeric
+string and allows some characters which are problematic for humans to be
+excluded. Due to this, there can be various base58 alphabets.
+
+The modified base58 alphabet used by Bitcoin, and hence this package, omits the
+0, O, I, and l characters that look the same in many fonts and are therefore
+hard to humans to distinguish.
+
+# Base58Check Encoding Scheme
+
+The Base58Check encoding scheme is primarily used for Bitcoin addresses at the
+time of this writing, however it can be used to generically encode arbitrary
+byte arrays into human-readable strings along with a version byte that can be
+used to differentiate the same payload. For Bitcoin addresses, the extra
+version is used to differentiate the network of otherwise identical public keys
+which helps prevent using an address intended for one network on another.
+*/
+package base58
diff --git a/vendor/maunium.net/go/mautrix/util/dualerror.go b/vendor/maunium.net/go/mautrix/util/dualerror.go
new file mode 100644
index 00000000..b153d432
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/util/dualerror.go
@@ -0,0 +1,33 @@
+// Copyright (c) 2022 Tulir Asokan
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package util
+
+import (
+ "errors"
+ "fmt"
+)
+
+type DualError struct {
+ High error
+ Low error
+}
+
+func NewDualError(high, low error) DualError {
+ return DualError{high, low}
+}
+
+func (err DualError) Is(other error) bool {
+ return errors.Is(other, err.High) || errors.Is(other, err.Low)
+}
+
+func (err DualError) Unwrap() error {
+ return err.Low
+}
+
+func (err DualError) Error() string {
+ return fmt.Sprintf("%v: %v", err.High, err.Low)
+}
diff --git a/vendor/maunium.net/go/mautrix/util/gjson.go b/vendor/maunium.net/go/mautrix/util/gjson.go
new file mode 100644
index 00000000..05ef8277
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/util/gjson.go
@@ -0,0 +1,31 @@
+// Copyright (c) 2022 Tulir Asokan
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package util
+
+import (
+ "strings"
+)
+
+var GJSONEscaper = strings.NewReplacer(
+ `\`, `\\`,
+ ".", `\.`,
+ "|", `\|`,
+ "#", `\#`,
+ "@", `\@`,
+ "*", `\*`,
+ "?", `\?`)
+
+func GJSONPath(path ...string) string {
+ var result strings.Builder
+ for i, part := range path {
+ _, _ = GJSONEscaper.WriteString(&result, part)
+ if i < len(path)-1 {
+ result.WriteRune('.')
+ }
+ }
+ return result.String()
+}
diff --git a/vendor/maunium.net/go/mautrix/util/jsontime/jsontime.go b/vendor/maunium.net/go/mautrix/util/jsontime/jsontime.go
new file mode 100644
index 00000000..b3282338
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/util/jsontime/jsontime.go
@@ -0,0 +1,86 @@
+// Copyright (c) 2022 Tulir Asokan
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package jsontime
+
+import (
+ "encoding/json"
+ "time"
+)
+
+func UM(time time.Time) UnixMilli {
+ return UnixMilli{Time: time}
+}
+
+func UMInt(ts int64) UnixMilli {
+ return UM(time.UnixMilli(ts))
+}
+
+func UnixMilliNow() UnixMilli {
+ return UM(time.Now())
+}
+
+type UnixMilli struct {
+ time.Time
+}
+
+func (um UnixMilli) MarshalJSON() ([]byte, error) {
+ if um.IsZero() {
+ return []byte{'0'}, nil
+ }
+ return json.Marshal(um.UnixMilli())
+}
+
+func (um *UnixMilli) UnmarshalJSON(data []byte) error {
+ var val int64
+ err := json.Unmarshal(data, &val)
+ if err != nil {
+ return err
+ }
+ if val == 0 {
+ um.Time = time.Time{}
+ } else {
+ um.Time = time.UnixMilli(val)
+ }
+ return nil
+}
+
+func U(time time.Time) Unix {
+ return Unix{Time: time}
+}
+
+func UInt(ts int64) Unix {
+ return U(time.Unix(ts, 0))
+}
+
+func UnixNow() Unix {
+ return U(time.Now())
+}
+
+type Unix struct {
+ time.Time
+}
+
+func (u Unix) MarshalJSON() ([]byte, error) {
+ if u.IsZero() {
+ return []byte{'0'}, nil
+ }
+ return json.Marshal(u.Unix())
+}
+
+func (u *Unix) UnmarshalJSON(data []byte) error {
+ var val int64
+ err := json.Unmarshal(data, &val)
+ if err != nil {
+ return err
+ }
+ if val == 0 {
+ u.Time = time.Time{}
+ } else {
+ u.Time = time.Unix(val, 0)
+ }
+ return nil
+}
diff --git a/vendor/maunium.net/go/mautrix/util/marshal.go b/vendor/maunium.net/go/mautrix/util/marshal.go
new file mode 100644
index 00000000..180adef2
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/util/marshal.go
@@ -0,0 +1,30 @@
+package util
+
+import (
+ "encoding/json"
+ "fmt"
+
+ "github.com/tidwall/gjson"
+ "github.com/tidwall/sjson"
+)
+
+// MarshalAndDeleteEmpty marshals a JSON object, then uses gjson to delete empty objects at the given gjson paths.
+//
+// This can be used as a convenient way to create a marshaler that omits empty non-pointer structs.
+// See mautrix.RespSync for example.
+func MarshalAndDeleteEmpty(marshalable interface{}, paths []string) ([]byte, error) {
+ data, err := json.Marshal(marshalable)
+ if err != nil {
+ return nil, err
+ }
+ for _, path := range paths {
+ res := gjson.GetBytes(data, path)
+ if res.IsObject() && len(res.Raw) == 2 {
+ data, err = sjson.DeleteBytes(data, path)
+ if err != nil {
+ return nil, fmt.Errorf("failed to delete empty %s: %w", path, err)
+ }
+ }
+ }
+ return data, nil
+}
diff --git a/vendor/maunium.net/go/mautrix/util/mimetypes.go b/vendor/maunium.net/go/mautrix/util/mimetypes.go
new file mode 100644
index 00000000..9877c3d3
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/util/mimetypes.go
@@ -0,0 +1,50 @@
+// Copyright (c) 2022 Sumner Evans
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package util
+
+import (
+ "mime"
+ "strings"
+)
+
+// MimeExtensionSanityOverrides includes extensions for various common mimetypes.
+//
+// This is necessary because sometimes the OS mimetype database and Go interact in weird ways,
+// which causes very obscure extensions to be first in the array for common mimetypes
+// (e.g. image/jpeg -> .jpe, text/plain -> ,v).
+var MimeExtensionSanityOverrides = map[string]string{
+ "image/png": ".png",
+ "image/webp": ".webp",
+ "image/jpeg": ".jpg",
+ "image/tiff": ".tiff",
+ "image/heif": ".heic",
+ "image/heic": ".heic",
+
+ "audio/mpeg": ".mp3",
+ "audio/ogg": ".ogg",
+ "audio/webm": ".webm",
+ "audio/x-caf": ".caf",
+ "video/mp4": ".mp4",
+ "video/mpeg": ".mpeg",
+ "video/webm": ".webm",
+
+ "text/plain": ".txt",
+ "text/html": ".html",
+
+ "application/xml": ".xml",
+}
+
+func ExtensionFromMimetype(mimetype string) string {
+ ext, ok := MimeExtensionSanityOverrides[strings.Split(mimetype, ";")[0]]
+ if !ok {
+ exts, _ := mime.ExtensionsByType(mimetype)
+ if len(exts) > 0 {
+ ext = exts[0]
+ }
+ }
+ return ext
+}
diff --git a/vendor/maunium.net/go/mautrix/util/random.go b/vendor/maunium.net/go/mautrix/util/random.go
new file mode 100644
index 00000000..944dd034
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/util/random.go
@@ -0,0 +1,65 @@
+// Copyright (c) 2022 Tulir Asokan
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package util
+
+import (
+ "crypto/rand"
+ "encoding/base64"
+ "hash/crc32"
+ "strings"
+ "unsafe"
+)
+
+func RandomBytes(n int) []byte {
+ data := make([]byte, n)
+ _, err := rand.Read(data)
+ if err != nil {
+ panic(err)
+ }
+ return data
+}
+
+var letters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
+
+// RandomString generates a random string of the given length.
+func RandomString(n int) string {
+ if n <= 0 {
+ return ""
+ }
+ base64Len := n
+ if n%4 != 0 {
+ base64Len += 4 - (n % 4)
+ }
+ decodedLength := base64.RawStdEncoding.DecodedLen(base64Len)
+ output := make([]byte, base64Len)
+ base64.RawStdEncoding.Encode(output, RandomBytes(decodedLength))
+ for i, char := range output {
+ if char == '+' || char == '/' {
+ _, err := rand.Read(output[i : i+1])
+ if err != nil {
+ panic(err)
+ }
+ output[i] = letters[int(output[i])%len(letters)]
+ }
+ }
+ return (*(*string)(unsafe.Pointer(&output)))[:n]
+}
+
+func base62Encode(val uint32, minWidth int) string {
+ var buf strings.Builder
+ for val > 0 {
+ buf.WriteByte(letters[val%62])
+ val /= 62
+ }
+ return strings.Repeat("0", minWidth-buf.Len()) + buf.String()
+}
+
+func RandomToken(namespace string, randomLength int) string {
+ token := namespace + "_" + RandomString(randomLength)
+ checksum := base62Encode(crc32.ChecksumIEEE([]byte(token)), 6)
+ return token + "_" + checksum
+}
diff --git a/vendor/maunium.net/go/mautrix/util/returnonce.go b/vendor/maunium.net/go/mautrix/util/returnonce.go
new file mode 100644
index 00000000..c85aae0a
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/util/returnonce.go
@@ -0,0 +1,23 @@
+// Copyright (c) 2023 Tulir Asokan
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package util
+
+import "sync"
+
+// ReturnableOnce is a wrapper for sync.Once that can return a value
+type ReturnableOnce[Value any] struct {
+ once sync.Once
+ output Value
+ err error
+}
+
+func (ronce *ReturnableOnce[Value]) Do(fn func() (Value, error)) (Value, error) {
+ ronce.once.Do(func() {
+ ronce.output, ronce.err = fn()
+ })
+ return ronce.output, ronce.err
+}
diff --git a/vendor/maunium.net/go/mautrix/util/ringbuffer.go b/vendor/maunium.net/go/mautrix/util/ringbuffer.go
new file mode 100644
index 00000000..ddb4c79f
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/util/ringbuffer.go
@@ -0,0 +1,77 @@
+// Copyright (c) 2023 Tulir Asokan
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package util
+
+import (
+ "sync"
+)
+
+type pair[Key comparable, Value any] struct {
+ Key Key
+ Value Value
+}
+
+type RingBuffer[Key comparable, Value any] struct {
+ ptr int
+ data []pair[Key, Value]
+ lock sync.RWMutex
+}
+
+func NewRingBuffer[Key comparable, Value any](size int) *RingBuffer[Key, Value] {
+ return &RingBuffer[Key, Value]{
+ data: make([]pair[Key, Value], size),
+ }
+}
+
+func (rb *RingBuffer[Key, Value]) Contains(val Key) bool {
+ _, ok := rb.Get(val)
+ return ok
+}
+
+func (rb *RingBuffer[Key, Value]) Get(key Key) (val Value, found bool) {
+ rb.lock.RLock()
+ end := rb.ptr
+ for i := clamp(end-1, len(rb.data)); i != end; i = clamp(i-1, len(rb.data)) {
+ if rb.data[i].Key == key {
+ val = rb.data[i].Value
+ found = true
+ break
+ }
+ }
+ rb.lock.RUnlock()
+ return
+}
+
+func (rb *RingBuffer[Key, Value]) Replace(key Key, val Value) bool {
+ rb.lock.Lock()
+ defer rb.lock.Unlock()
+ end := rb.ptr
+ for i := clamp(end-1, len(rb.data)); i != end; i = clamp(i-1, len(rb.data)) {
+ if rb.data[i].Key == key {
+ rb.data[i].Value = val
+ return true
+ }
+ }
+ return false
+}
+
+func (rb *RingBuffer[Key, Value]) Push(key Key, val Value) {
+ rb.lock.Lock()
+ rb.data[rb.ptr] = pair[Key, Value]{Key: key, Value: val}
+ rb.ptr = (rb.ptr + 1) % len(rb.data)
+ rb.lock.Unlock()
+}
+
+func clamp(index, len int) int {
+ if index < 0 {
+ return len + index
+ } else if index >= len {
+ return len - index
+ } else {
+ return index
+ }
+}
diff --git a/vendor/maunium.net/go/mautrix/util/syncmap.go b/vendor/maunium.net/go/mautrix/util/syncmap.go
new file mode 100644
index 00000000..a6b20d3b
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/util/syncmap.go
@@ -0,0 +1,94 @@
+// Copyright (c) 2023 Tulir Asokan
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package util
+
+import "sync"
+
+// SyncMap is a simple map with a built-in mutex.
+type SyncMap[Key comparable, Value any] struct {
+ data map[Key]Value
+ lock sync.RWMutex
+}
+
+func NewSyncMap[Key comparable, Value any]() *SyncMap[Key, Value] {
+ return &SyncMap[Key, Value]{
+ data: make(map[Key]Value),
+ }
+}
+
+// Set stores a value in the map.
+func (sm *SyncMap[Key, Value]) Set(key Key, value Value) {
+ sm.Swap(key, value)
+}
+
+// Swap sets a value in the map and returns the old value.
+//
+// The boolean return parameter is true if the value already existed, false if not.
+func (sm *SyncMap[Key, Value]) Swap(key Key, value Value) (oldValue Value, wasReplaced bool) {
+ sm.lock.Lock()
+ oldValue, wasReplaced = sm.data[key]
+ sm.data[key] = value
+ sm.lock.Unlock()
+ return
+}
+
+// Delete removes a key from the map.
+func (sm *SyncMap[Key, Value]) Delete(key Key) {
+ sm.Pop(key)
+}
+
+// Pop removes a key from the map and returns the old value.
+//
+// The boolean return parameter is the same as with normal Go map access (true if the key exists, false if not).
+func (sm *SyncMap[Key, Value]) Pop(key Key) (value Value, ok bool) {
+ sm.lock.Lock()
+ value, ok = sm.data[key]
+ delete(sm.data, key)
+ sm.lock.Unlock()
+ return
+}
+
+// Get gets a value in the map.
+//
+// The boolean return parameter is the same as with normal Go map access (true if the key exists, false if not).
+func (sm *SyncMap[Key, Value]) Get(key Key) (value Value, ok bool) {
+ sm.lock.RLock()
+ value, ok = sm.data[key]
+ sm.lock.RUnlock()
+ return
+}
+
+// GetOrSet gets a value in the map if the key already exists, otherwise inserts the given value and returns it.
+//
+// The boolean return parameter is true if the key already exists, and false if the given value was inserted.
+func (sm *SyncMap[Key, Value]) GetOrSet(key Key, value Value) (actual Value, wasGet bool) {
+ sm.lock.Lock()
+ defer sm.lock.Unlock()
+ actual, wasGet = sm.data[key]
+ if wasGet {
+ return
+ }
+ sm.data[key] = value
+ actual = value
+ return
+}
+
+// Clone returns a copy of the map.
+func (sm *SyncMap[Key, Value]) Clone() *SyncMap[Key, Value] {
+ return &SyncMap[Key, Value]{data: sm.CopyData()}
+}
+
+// CopyData returns a copy of the data in the map as a normal (non-atomic) map.
+func (sm *SyncMap[Key, Value]) CopyData() map[Key]Value {
+ sm.lock.RLock()
+ copied := make(map[Key]Value, len(sm.data))
+ for key, value := range sm.data {
+ copied[key] = value
+ }
+ sm.lock.RUnlock()
+ return copied
+}
diff --git a/vendor/maunium.net/go/mautrix/version.go b/vendor/maunium.net/go/mautrix/version.go
new file mode 100644
index 00000000..8d3c7c01
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/version.go
@@ -0,0 +1,5 @@
+package mautrix
+
+const Version = "v0.15.0"
+
+var DefaultUserAgent = "mautrix-go/" + Version
diff --git a/vendor/maunium.net/go/mautrix/versions.go b/vendor/maunium.net/go/mautrix/versions.go
new file mode 100644
index 00000000..1eb406c0
--- /dev/null
+++ b/vendor/maunium.net/go/mautrix/versions.go
@@ -0,0 +1,153 @@
+// Copyright (c) 2022 Tulir Asokan
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+package mautrix
+
+import (
+ "fmt"
+ "regexp"
+ "strconv"
+)
+
+// RespVersions is the JSON response for https://spec.matrix.org/v1.2/client-server-api/#get_matrixclientversions
+type RespVersions struct {
+ Versions []SpecVersion `json:"versions"`
+ UnstableFeatures map[string]bool `json:"unstable_features"`
+}
+
+func (versions *RespVersions) ContainsFunc(match func(found SpecVersion) bool) bool {
+ for _, found := range versions.Versions {
+ if match(found) {
+ return true
+ }
+ }
+ return false
+}
+
+func (versions *RespVersions) Contains(version SpecVersion) bool {
+ return versions.ContainsFunc(func(found SpecVersion) bool {
+ return found == version
+ })
+}
+
+func (versions *RespVersions) ContainsGreaterOrEqual(version SpecVersion) bool {
+ return versions.ContainsFunc(func(found SpecVersion) bool {
+ return found.GreaterThan(version) || found == version
+ })
+}
+
+func (versions *RespVersions) GetLatest() (latest SpecVersion) {
+ for _, ver := range versions.Versions {
+ if ver.GreaterThan(latest) {
+ latest = ver
+ }
+ }
+ return
+}
+
+type SpecVersionFormat int
+
+const (
+ SpecVersionFormatUnknown SpecVersionFormat = iota
+ SpecVersionFormatR
+ SpecVersionFormatV
+)
+
+var (
+ SpecR000 = MustParseSpecVersion("r0.0.0")
+ SpecR001 = MustParseSpecVersion("r0.0.1")
+ SpecR010 = MustParseSpecVersion("r0.1.0")
+ SpecR020 = MustParseSpecVersion("r0.2.0")
+ SpecR030 = MustParseSpecVersion("r0.3.0")
+ SpecR040 = MustParseSpecVersion("r0.4.0")
+ SpecR050 = MustParseSpecVersion("r0.5.0")
+ SpecR060 = MustParseSpecVersion("r0.6.0")
+ SpecR061 = MustParseSpecVersion("r0.6.1")
+ SpecV11 = MustParseSpecVersion("v1.1")
+ SpecV12 = MustParseSpecVersion("v1.2")
+ SpecV13 = MustParseSpecVersion("v1.3")
+ SpecV14 = MustParseSpecVersion("v1.4")
+ SpecV15 = MustParseSpecVersion("v1.5")
+)
+
+func (svf SpecVersionFormat) String() string {
+ switch svf {
+ case SpecVersionFormatR:
+ return "r"
+ case SpecVersionFormatV:
+ return "v"
+ default:
+ return ""
+ }
+}
+
+type SpecVersion struct {
+ Format SpecVersionFormat
+ Major int
+ Minor int
+ Patch int
+
+ Raw string
+}
+
+var legacyVersionRegex = regexp.MustCompile(`^r(\d+)\.(\d+)\.(\d+)$`)
+var modernVersionRegex = regexp.MustCompile(`^v(\d+)\.(\d+)$`)
+
+func MustParseSpecVersion(version string) SpecVersion {
+ sv, err := ParseSpecVersion(version)
+ if err != nil {
+ panic(err)
+ }
+ return sv
+}
+
+func ParseSpecVersion(version string) (sv SpecVersion, err error) {
+ sv.Raw = version
+ if parts := modernVersionRegex.FindStringSubmatch(version); parts != nil {
+ sv.Major, _ = strconv.Atoi(parts[1])
+ sv.Minor, _ = strconv.Atoi(parts[2])
+ sv.Format = SpecVersionFormatV
+ } else if parts = legacyVersionRegex.FindStringSubmatch(version); parts != nil {
+ sv.Major, _ = strconv.Atoi(parts[1])
+ sv.Minor, _ = strconv.Atoi(parts[2])
+ sv.Patch, _ = strconv.Atoi(parts[3])
+ sv.Format = SpecVersionFormatR
+ } else {
+ err = fmt.Errorf("version '%s' doesn't match either known syntax", version)
+ }
+ return
+}
+
+func (sv *SpecVersion) UnmarshalText(version []byte) error {
+ *sv, _ = ParseSpecVersion(string(version))
+ return nil
+}
+
+func (sv *SpecVersion) MarshalText() ([]byte, error) {
+ return []byte(sv.String()), nil
+}
+
+func (sv SpecVersion) String() string {
+ switch sv.Format {
+ case SpecVersionFormatR:
+ return fmt.Sprintf("r%d.%d.%d", sv.Major, sv.Minor, sv.Patch)
+ case SpecVersionFormatV:
+ return fmt.Sprintf("v%d.%d", sv.Major, sv.Minor)
+ default:
+ return sv.Raw
+ }
+}
+
+func (sv SpecVersion) LessThan(other SpecVersion) bool {
+ return sv != other && !sv.GreaterThan(other)
+}
+
+func (sv SpecVersion) GreaterThan(other SpecVersion) bool {
+ return sv.Format > other.Format ||
+ (sv.Format == other.Format && sv.Major > other.Major) ||
+ (sv.Format == other.Format && sv.Major == other.Major && sv.Minor > other.Minor) ||
+ (sv.Format == other.Format && sv.Major == other.Major && sv.Minor == other.Minor && sv.Patch > other.Patch)
+}