Matrix: switch to the mautrix-go library

Also: add initial support for [Matrix] application services
This commit is contained in:
Simon THOBY
2022-07-24 20:12:03 +02:00
committed by Simon Thoby
parent 89b0d362d2
commit 625d7cd94c
175 changed files with 31467 additions and 2873 deletions

374
vendor/maunium.net/go/maulogger/v2/LICENSE generated vendored Normal file
View File

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

6
vendor/maunium.net/go/maulogger/v2/README.md generated vendored Normal file
View File

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

284
vendor/maunium.net/go/maulogger/v2/defaults.go generated vendored Normal file
View File

@@ -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...)
}

47
vendor/maunium.net/go/maulogger/v2/level.go generated vendored Normal file
View File

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

224
vendor/maunium.net/go/maulogger/v2/logger.go generated vendored Normal file
View File

@@ -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()
}
}
}

View File

@@ -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...))
}

View File

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

216
vendor/maunium.net/go/maulogger/v2/sublogger.go generated vendored Normal file
View File

@@ -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...))
}

78
vendor/maunium.net/go/maulogger/v2/writer.go generated vendored Normal file
View File

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

12
vendor/maunium.net/go/mautrix/.editorconfig generated vendored Normal file
View File

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

4
vendor/maunium.net/go/mautrix/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,4 @@
.idea/
.vscode/
*.db
*.log

15
vendor/maunium.net/go/mautrix/.pre-commit-config.yaml generated vendored Normal file
View File

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

612
vendor/maunium.net/go/mautrix/CHANGELOG.md generated vendored Normal file
View File

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

373
vendor/maunium.net/go/mautrix/LICENSE generated vendored Normal file
View File

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

24
vendor/maunium.net/go/mautrix/README.md generated vendored Normal file
View File

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

350
vendor/maunium.net/go/mautrix/appservice/appservice.go generated vendored Normal file
View File

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

View File

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

348
vendor/maunium.net/go/mautrix/appservice/http.go generated vendored Normal file
View File

@@ -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("{}"))
}

419
vendor/maunium.net/go/mautrix/appservice/intent.go generated vendored Normal file
View File

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

152
vendor/maunium.net/go/mautrix/appservice/protocol.go generated vendored Normal file
View File

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

View File

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

43
vendor/maunium.net/go/mautrix/appservice/txnid.go generated vendored Normal file
View File

@@ -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()
}

408
vendor/maunium.net/go/mautrix/appservice/websocket.go generated vendored Normal file
View File

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

2023
vendor/maunium.net/go/mautrix/client.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

View File

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

133
vendor/maunium.net/go/mautrix/crypto/utils/utils.go generated vendored Normal file
View File

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

154
vendor/maunium.net/go/mautrix/error.go generated vendored Normal file
View File

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

45
vendor/maunium.net/go/mautrix/event/accountdata.go generated vendored Normal file
View File

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

51
vendor/maunium.net/go/mautrix/event/beeper.go generated vendored Normal file
View File

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

506
vendor/maunium.net/go/mautrix/event/content.go generated vendored Normal file
View File

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

149
vendor/maunium.net/go/mautrix/event/encryption.go generated vendored Normal file
View File

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

140
vendor/maunium.net/go/mautrix/event/ephemeral.go generated vendored Normal file
View File

@@ -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"`
}

149
vendor/maunium.net/go/mautrix/event/events.go generated vendored Normal file
View File

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

53
vendor/maunium.net/go/mautrix/event/member.go generated vendored Normal file
View File

@@ -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"`
}
}

276
vendor/maunium.net/go/mautrix/event/message.go generated vendored Normal file
View File

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

149
vendor/maunium.net/go/mautrix/event/powerlevels.go generated vendored Normal file
View File

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

234
vendor/maunium.net/go/mautrix/event/relations.go generated vendored Normal file
View File

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

100
vendor/maunium.net/go/mautrix/event/reply.go generated vendored Normal file
View File

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

176
vendor/maunium.net/go/mautrix/event/state.go generated vendored Normal file
View File

@@ -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"`
}

256
vendor/maunium.net/go/mautrix/event/type.go generated vendored Normal file
View File

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

307
vendor/maunium.net/go/mautrix/event/verification.go generated vendored Normal file
View File

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

116
vendor/maunium.net/go/mautrix/event/voip.go generated vendored Normal file
View File

@@ -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"`
}

93
vendor/maunium.net/go/mautrix/filter.go generated vendored Normal file
View File

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

158
vendor/maunium.net/go/mautrix/id/contenturi.go generated vendored Normal file
View File

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

149
vendor/maunium.net/go/mautrix/id/crypto.go generated vendored Normal file
View File

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

293
vendor/maunium.net/go/mautrix/id/matrixuri.go generated vendored Normal file
View File

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

83
vendor/maunium.net/go/mautrix/id/opaque.go generated vendored Normal file
View File

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

87
vendor/maunium.net/go/mautrix/id/trust.go generated vendored Normal file
View File

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

224
vendor/maunium.net/go/mautrix/id/userid.go generated vendored Normal file
View File

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

124
vendor/maunium.net/go/mautrix/pushrules/action.go generated vendored Normal file
View File

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

266
vendor/maunium.net/go/mautrix/pushrules/condition.go generated vendored Normal file
View File

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

2
vendor/maunium.net/go/mautrix/pushrules/doc.go generated vendored Normal file
View File

@@ -0,0 +1,2 @@
// Package pushrules contains utilities to parse push notification rules.
package pushrules

22
vendor/maunium.net/go/mautrix/pushrules/glob/LICENSE generated vendored Normal file
View File

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

28
vendor/maunium.net/go/mautrix/pushrules/glob/README.md generated vendored Normal file
View File

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

108
vendor/maunium.net/go/mautrix/pushrules/glob/glob.go generated vendored Normal file
View File

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

37
vendor/maunium.net/go/mautrix/pushrules/pushrules.go generated vendored Normal file
View File

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

154
vendor/maunium.net/go/mautrix/pushrules/rule.go generated vendored Normal file
View File

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

88
vendor/maunium.net/go/mautrix/pushrules/ruleset.go generated vendored Normal file
View File

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

437
vendor/maunium.net/go/mautrix/requests.go generated vendored Normal file
View File

@@ -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"`
}

600
vendor/maunium.net/go/mautrix/responses.go generated vendored Normal file
View File

@@ -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"`
}

54
vendor/maunium.net/go/mautrix/room.go generated vendored Normal file
View File

@@ -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),
}
}

221
vendor/maunium.net/go/mautrix/statestore.go generated vendored Normal file
View File

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

310
vendor/maunium.net/go/mautrix/sync.go generated vendored Normal file
View File

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

149
vendor/maunium.net/go/mautrix/syncstore.go generated vendored Normal file
View File

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

106
vendor/maunium.net/go/mautrix/url.go generated vendored Normal file
View File

@@ -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()
}

9
vendor/maunium.net/go/mautrix/util/base58/README.md generated vendored Normal file
View File

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

49
vendor/maunium.net/go/mautrix/util/base58/alphabet.go generated vendored Normal file
View File

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

138
vendor/maunium.net/go/mautrix/util/base58/base58.go generated vendored Normal file
View File

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

View File

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

29
vendor/maunium.net/go/mautrix/util/base58/doc.go generated vendored Normal file
View File

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

33
vendor/maunium.net/go/mautrix/util/dualerror.go generated vendored Normal file
View File

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

31
vendor/maunium.net/go/mautrix/util/gjson.go generated vendored Normal file
View File

@@ -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()
}

View File

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

30
vendor/maunium.net/go/mautrix/util/marshal.go generated vendored Normal file
View File

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

50
vendor/maunium.net/go/mautrix/util/mimetypes.go generated vendored Normal file
View File

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

65
vendor/maunium.net/go/mautrix/util/random.go generated vendored Normal file
View File

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

23
vendor/maunium.net/go/mautrix/util/returnonce.go generated vendored Normal file
View File

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

77
vendor/maunium.net/go/mautrix/util/ringbuffer.go generated vendored Normal file
View File

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

94
vendor/maunium.net/go/mautrix/util/syncmap.go generated vendored Normal file
View File

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

5
vendor/maunium.net/go/mautrix/version.go generated vendored Normal file
View File

@@ -0,0 +1,5 @@
package mautrix
const Version = "v0.15.0"
var DefaultUserAgent = "mautrix-go/" + Version

153
vendor/maunium.net/go/mautrix/versions.go generated vendored Normal file
View File

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