This commit is contained in:
Tulir Asokan
2018-08-26 17:29:51 +03:00
parent 941ab724c6
commit a6ebc50f6d
499 changed files with 462188 additions and 2 deletions

1
vendor/maunium.net/go/mautrix-appservice/.gitignore generated vendored Normal file
View File

@ -0,0 +1 @@
.idea/

21
vendor/maunium.net/go/mautrix-appservice/LICENSE generated vendored Normal file
View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2018 Tulir Asokan
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.

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

@ -0,0 +1,9 @@
# matrix-appservice-go
A [Matrix](https://matrix.org) [Application Service](https://matrix.org/docs/guides/application_services.html) framework written in Go.
This is highly work in progress, but the base appservice framework should be somewhat usable.
## Installation
```bash
go get -u maunium.net/go/mautrix-appservice-go
```

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

@ -0,0 +1,286 @@
package appservice
import (
"fmt"
"io/ioutil"
"os"
"gopkg.in/yaml.v2"
"maunium.net/go/maulogger"
"strings"
"net/http"
"errors"
"maunium.net/go/gomatrix"
"regexp"
)
// EventChannelSize is the size for the Events channel in Appservice instances.
var EventChannelSize = 64
// Create a blank appservice instance.
func Create() *AppService {
return &AppService{
LogConfig: CreateLogConfig(),
clients: make(map[string]*gomatrix.Client),
intents: make(map[string]*IntentAPI),
StateStore: &BasicStateStore{},
}
}
// Load an appservice config from a file.
func Load(path string) (*AppService, error) {
data, readErr := ioutil.ReadFile(path)
if readErr != nil {
return nil, readErr
}
var config = &AppService{}
yaml.Unmarshal(data, config)
return config, nil
}
// QueryHandler handles room alias and user ID queries from the homeserver.
type QueryHandler interface {
QueryAlias(alias string) bool
QueryUser(userID string) bool
}
type QueryHandlerStub struct{}
func (qh *QueryHandlerStub) QueryAlias(alias string) bool {
return false
}
func (qh *QueryHandlerStub) QueryUser(userID string) bool {
return false
}
// AppService is the main config for all appservices.
// It also serves as the appservice instance struct.
type AppService struct {
HomeserverDomain string `yaml:"homeserver_domain"`
HomeserverURL string `yaml:"homeserver_url"`
RegistrationPath string `yaml:"registration"`
Host HostConfig `yaml:"host"`
LogConfig LogConfig `yaml:"logging"`
Registration *Registration `yaml:"-"`
Log maulogger.Logger `yaml:"-"`
lastProcessedTransaction string
Events chan *gomatrix.Event `yaml:"-"`
QueryHandler QueryHandler `yaml:"-"`
StateStore StateStore `yaml:"-"`
server *http.Server
botClient *gomatrix.Client
botIntent *IntentAPI
clients map[string]*gomatrix.Client
intents map[string]*IntentAPI
}
// 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)
}
// 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 ioutil.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() string {
return fmt.Sprintf("@%s:%s", as.Registration.SenderLocalpart, as.HomeserverDomain)
}
var MatrixUserIDRegex = regexp.MustCompile("^@([^:]+):(.+)$")
func ParseUserID(mxid string) (string, string) {
match := MatrixUserIDRegex.FindStringSubmatch(mxid)
if match != nil && len(match) == 3 {
return match[1], match[2]
}
return "", ""
}
func (as *AppService) Intent(userID string) *IntentAPI {
intent, ok := as.intents[userID]
if !ok {
localpart, homeserver := ParseUserID(userID)
if len(localpart) == 0 || homeserver != as.HomeserverDomain {
return nil
}
intent = as.NewIntentAPI(localpart)
as.intents[userID] = intent
}
return intent
}
func (as *AppService) BotIntent() *IntentAPI {
if as.botIntent == nil {
as.botIntent = as.NewIntentAPI(as.Registration.SenderLocalpart)
}
return as.botIntent
}
func (as *AppService) Client(userID string) *gomatrix.Client {
client, ok := as.clients[userID]
if !ok {
var err error
client, err = gomatrix.NewClient(as.HomeserverURL, userID, as.Registration.AppToken)
if err != nil {
as.Log.Fatalln("Failed to create gomatrix instance:", err)
return nil
}
client.Syncer = nil
client.Store = nil
client.AppServiceUserID = userID
client.Logger = as.Log.Sub(userID)
as.clients[userID] = client
}
return client
}
func (as *AppService) BotClient() *gomatrix.Client {
if as.botClient == nil {
var err error
as.botClient, err = gomatrix.NewClient(as.HomeserverURL, as.BotMXID(), as.Registration.AppToken)
if err != nil {
as.Log.Fatalln("Failed to create gomatrix instance:", err)
return nil
}
as.botClient.Syncer = nil
as.botClient.Store = nil
as.botClient.Logger = as.Log.Sub("Bot")
}
return as.botClient
}
// Init initializes the logger and loads the registration of this appservice.
func (as *AppService) Init() (bool, error) {
as.Events = make(chan *gomatrix.Event, EventChannelSize)
as.QueryHandler = &QueryHandlerStub{}
as.Log = maulogger.Create()
as.LogConfig.Configure(as.Log)
as.Log.Debugln("Logger initialized successfully.")
if len(as.RegistrationPath) > 0 {
var err error
as.Registration, err = LoadRegistration(as.RegistrationPath)
if err != nil {
return false, err
}
}
as.Log.Debugln("Appservice initialized successfully.")
return true, nil
}
// LogConfig contains configs for the logger.
type LogConfig struct {
Directory string `yaml:"directory"`
FileNameFormat string `yaml:"file_name_format"`
FileDateFormat string `yaml:"file_date_format"`
FileMode uint32 `yaml:"file_mode"`
TimestampFormat string `yaml:"timestamp_format"`
RawPrintLevel string `yaml:"print_level"`
PrintLevel int `yaml:"-"`
}
type umLogConfig LogConfig
func (lc *LogConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
err := unmarshal((*umLogConfig)(lc))
if err != nil {
return err
}
switch strings.ToUpper(lc.RawPrintLevel) {
case "DEBUG":
lc.PrintLevel = maulogger.LevelDebug.Severity
case "INFO":
lc.PrintLevel = maulogger.LevelInfo.Severity
case "WARN", "WARNING":
lc.PrintLevel = maulogger.LevelWarn.Severity
case "ERR", "ERROR":
lc.PrintLevel = maulogger.LevelError.Severity
case "FATAL":
lc.PrintLevel = maulogger.LevelFatal.Severity
default:
return errors.New("invalid print level " + lc.RawPrintLevel)
}
return err
}
func (lc *LogConfig) MarshalYAML() (interface{}, error) {
switch {
case lc.PrintLevel >= maulogger.LevelFatal.Severity:
lc.RawPrintLevel = maulogger.LevelFatal.Name
case lc.PrintLevel >= maulogger.LevelError.Severity:
lc.RawPrintLevel = maulogger.LevelError.Name
case lc.PrintLevel >= maulogger.LevelWarn.Severity:
lc.RawPrintLevel = maulogger.LevelWarn.Name
case lc.PrintLevel >= maulogger.LevelInfo.Severity:
lc.RawPrintLevel = maulogger.LevelInfo.Name
default:
lc.RawPrintLevel = maulogger.LevelDebug.Name
}
return lc, nil
}
// CreateLogConfig creates a basic LogConfig.
func CreateLogConfig() LogConfig {
return LogConfig{
Directory: "./logs",
FileNameFormat: "%[1]s-%02[2]d.log",
TimestampFormat: "Jan _2, 2006 15:04:05",
FileMode: 0600,
FileDateFormat: "2006-01-02",
PrintLevel: 10,
}
}
// GetFileFormat returns a mauLogger-compatible logger file format based on the data in the struct.
func (lc LogConfig) GetFileFormat() maulogger.LoggerFileFormat {
path := lc.FileNameFormat
if len(lc.Directory) > 0 {
path = lc.Directory + "/" + path
}
return func(now string, i int) string {
return fmt.Sprintf(path, now, i)
}
}
// Configure configures a mauLogger instance with the data in this struct.
func (lc LogConfig) Configure(log maulogger.Logger) {
basicLogger := log.(*maulogger.BasicLogger)
basicLogger.FileFormat = lc.GetFileFormat()
basicLogger.FileMode = os.FileMode(lc.FileMode)
basicLogger.FileTimeFormat = lc.FileDateFormat
basicLogger.TimeFormat = lc.TimestampFormat
basicLogger.PrintLevel = lc.PrintLevel
}

View File

@ -0,0 +1,77 @@
package appservice
import (
"maunium.net/go/gomatrix"
log "maunium.net/go/maulogger"
)
type ExecMode uint8
const (
AsyncHandlers ExecMode = iota
AsyncLoop
Sync
)
type EventProcessor struct {
ExecMode ExecMode
as *AppService
log log.Logger
stop chan struct{}
handlers map[gomatrix.EventType][]gomatrix.OnEventListener
}
func NewEventProcessor(as *AppService) *EventProcessor {
return &EventProcessor{
ExecMode: AsyncHandlers,
as: as,
log: as.Log.Sub("Events"),
stop: make(chan struct{}, 1),
handlers: make(map[gomatrix.EventType][]gomatrix.OnEventListener),
}
}
func (ep *EventProcessor) On(evtType gomatrix.EventType, handler gomatrix.OnEventListener) {
handlers, ok := ep.handlers[evtType]
if !ok {
handlers = []gomatrix.OnEventListener{handler}
} else {
handlers = append(handlers, handler)
}
ep.handlers[evtType] = handlers
}
func (ep *EventProcessor) Start() {
for {
select {
case evt := <-ep.as.Events:
handlers, ok := ep.handlers[evt.Type]
if !ok {
continue
}
switch ep.ExecMode {
case AsyncHandlers:
for _, handler := range handlers {
go handler(evt)
}
case AsyncLoop:
go func() {
for _, handler := range handlers {
handler(evt)
}
}()
case Sync:
for _, handler := range handlers {
handler(evt)
}
}
case <-ep.stop:
return
}
}
}
func (ep *EventProcessor) Stop() {
ep.stop <- struct{}{}
}

208
vendor/maunium.net/go/mautrix-appservice/generator.go generated vendored Normal file
View File

@ -0,0 +1,208 @@
package appservice
import (
"bufio"
"fmt"
"os"
"regexp"
"strconv"
"strings"
"github.com/fatih/color"
)
func readString(reader *bufio.Reader, message, defaultValue string) (string, error) {
color.Green(message)
if len(defaultValue) > 0 {
fmt.Printf("[%s]", defaultValue)
}
fmt.Print("> ")
val, err := reader.ReadString('\n')
if err != nil {
return "", err
}
val = strings.TrimSuffix(val, "\n")
if len(val) == 0 {
return defaultValue, nil
}
val = strings.TrimSuffix(val, "\r")
if len(val) == 0 {
return defaultValue, nil
}
return val, nil
}
const (
yes = "yes"
yesShort = "y"
)
// GenerateRegistration asks the user questions and generates a config and registration based on the answers.
func GenerateRegistration(asName, botName string, reserveRooms, reserveUsers bool) {
var boldCyan = color.New(color.FgCyan).Add(color.Bold)
var boldGreen = color.New(color.FgGreen).Add(color.Bold)
boldCyan.Println("Generating appservice config and registration.")
reader := bufio.NewReader(os.Stdin)
name, err := readString(reader, "Enter name for appservice", asName)
if err != nil {
fmt.Println("Failed to read user Input:", err)
return
}
registration := CreateRegistration(name)
config := Create()
registration.RateLimited = false
registration.SenderLocalpart, err = readString(reader, "Enter bot username", botName)
if err != nil {
fmt.Println("Failed to read user Input:", err)
return
}
asProtocol, err := readString(reader, "Enter appservice host protocol", "http")
if err != nil {
fmt.Println("Failed to read user Input:", err)
return
}
if asProtocol == "https" {
sslInput, err := readString(reader, "Do you want the appservice to handle SSL [yes/no]?", "yes")
if err != nil {
fmt.Println("Failed to read user Input:", err)
return
}
wantSSL := strings.ToLower(sslInput)
if wantSSL == yes {
config.Host.TLSCert, err = readString(reader, "Enter path to SSL certificate", "appservice.crt")
if err != nil {
fmt.Println("Failed to read user Input:", err)
return
}
config.Host.TLSKey, err = readString(reader, "Enter path to SSL key", "appservice.key")
if err != nil {
fmt.Println("Failed to read user Input:", err)
return
}
}
}
asHostname, err := readString(reader, "Enter appservice hostname", "localhost")
if err != nil {
fmt.Println("Failed to read user Input:", err)
return
}
asInput, err := readString(reader, "Enter appservice host port", "29313")
if err != nil {
fmt.Println("Failed to read user Input:", err)
return
}
asPort, convErr := strconv.Atoi(asInput)
if convErr != nil {
fmt.Println("Failed to parse port:", convErr)
return
}
registration.URL = fmt.Sprintf("%s://%s:%d", asProtocol, asHostname, asPort)
config.Host.Hostname = asHostname
config.Host.Port = uint16(asPort)
config.HomeserverURL, err = readString(reader, "Enter homeserver address", "http://localhost:8008")
if err != nil {
fmt.Println("Failed to read user Input:", err)
return
}
config.HomeserverDomain, err = readString(reader, "Enter homeserver domain", "example.com")
if err != nil {
fmt.Println("Failed to read user Input:", err)
return
}
config.LogConfig.Directory, err = readString(reader, "Enter directory for logs", "./logs")
if err != nil {
fmt.Println("Failed to read user Input:", err)
return
}
os.MkdirAll(config.LogConfig.Directory, 0755)
if reserveRooms || reserveUsers {
for {
namespace, err := readString(reader, "Enter namespace prefix", fmt.Sprintf("_%s_", name))
if err != nil {
fmt.Println("Failed to read user Input:", err)
return
}
roomNamespaceRegex, err := regexp.Compile(fmt.Sprintf("#%s.+:%s", namespace, config.HomeserverDomain))
if err != nil {
fmt.Println(err)
continue
}
userNamespaceRegex, regexpErr := regexp.Compile(fmt.Sprintf("@%s.+:%s", namespace, config.HomeserverDomain))
if regexpErr != nil {
fmt.Println("Failed to generate regexp for the userNamespace:", err)
return
}
if reserveRooms {
registration.Namespaces.RegisterRoomAliases(roomNamespaceRegex, true)
}
if reserveUsers {
registration.Namespaces.RegisterUserIDs(userNamespaceRegex, true)
}
break
}
}
boldCyan.Println("\n==== Registration generated ====")
yamlString, yamlErr := registration.YAML()
if err != nil {
fmt.Println("Failed to return the registration Config:", yamlErr)
return
}
color.Yellow(yamlString)
okInput, readErr := readString(reader, "Does the registration look OK [yes/no]?", "yes")
if readErr != nil {
fmt.Println("Failed to read user Input:", readErr)
return
}
ok := strings.ToLower(okInput)
if ok != yesShort && ok != yes {
fmt.Println("Cancelling generation.")
return
}
path, err := readString(reader, "Where should the registration be saved?", "registration.yaml")
if err != nil {
fmt.Println("Failed to read user Input:", err)
return
}
err = registration.Save(path)
if err != nil {
fmt.Println("Failed to save registration:", err)
return
}
boldGreen.Println("Registration saved.")
config.RegistrationPath = path
boldCyan.Println("\n======= Config generated =======")
color.Yellow(config.YAML())
okString, err := readString(reader, "Does the config look OK [yes/no]?", "yes")
if err != nil {
fmt.Println("Failed to read user Input:", err)
return
}
ok = strings.ToLower(okString)
if ok != yesShort && ok != yes {
fmt.Println("Cancelling generation.")
return
}
path, err = readString(reader, "Where should the config be saved?", "config.yaml")
if err != nil {
fmt.Println("Failed to read user Input:", err)
return
}
err = config.Save(path)
if err != nil {
fmt.Println("Failed to save config:", err)
return
}
boldGreen.Println("Config saved.")
}

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

@ -0,0 +1,153 @@
package appservice
import (
"encoding/json"
"io/ioutil"
"net/http"
"github.com/gorilla/mux"
"context"
"time"
)
// Listen starts the HTTP server that listens for calls from the Matrix homeserver.
func (as *AppService) Start() {
r := mux.NewRouter()
r.HandleFunc("/transactions/{txnID}", as.PutTransaction).Methods(http.MethodPut)
r.HandleFunc("/rooms/{roomAlias}", as.GetRoom).Methods(http.MethodGet)
r.HandleFunc("/users/{userID}", as.GetUser).Methods(http.MethodGet)
var err error
as.server = &http.Server{
Addr: as.Host.Address(),
Handler: r,
}
as.Log.Infoln("Listening on", as.Host.Address())
if len(as.Host.TLSCert) == 0 || len(as.Host.TLSKey) == 0 {
err = as.server.ListenAndServe()
} else {
err = as.server.ListenAndServeTLS(as.Host.TLSCert, as.Host.TLSKey)
}
if err != nil && err.Error() != "http: Server closed" {
as.Log.Fatalln("Error while listening:", err)
} else {
as.Log.Debugln("Listener stopped.")
}
}
func (as *AppService) Stop() {
if as.server == nil {
return
}
ctx, _ := context.WithTimeout(context.Background(), 5*time.Second)
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) bool {
query := r.URL.Query()
val, ok := query["access_token"]
if !ok {
Error{
ErrorCode: ErrForbidden,
HTTPStatus: http.StatusForbidden,
Message: "Bad token supplied.",
}.Write(w)
return false
}
for _, str := range val {
return str == as.Registration.ServerToken
}
return false
}
// 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 := ioutil.ReadAll(r.Body)
if err != nil || len(body) == 0 {
Error{
ErrorCode: ErrNoBody,
HTTPStatus: http.StatusBadRequest,
Message: "Missing request body.",
}.Write(w)
return
}
if as.lastProcessedTransaction == txnID {
// Duplicate transaction ID: no-op
WriteBlankOK(w)
return
}
eventList := EventList{}
err = json.Unmarshal(body, &eventList)
if err != nil {
Error{
ErrorCode: ErrInvalidJSON,
HTTPStatus: http.StatusBadRequest,
Message: "Failed to parse body JSON.",
}.Write(w)
return
}
for _, event := range eventList.Events {
as.Log.Debugln("Received event", event.ID)
as.UpdateState(event)
as.Events <- event
}
as.lastProcessedTransaction = txnID
WriteBlankOK(w)
}
// 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 := vars["userID"]
ok := as.QueryHandler.QueryUser(userID)
if ok {
WriteBlankOK(w)
} else {
Error{
ErrorCode: ErrUnknown,
HTTPStatus: http.StatusNotFound,
}.Write(w)
}
}

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

@ -0,0 +1,218 @@
package appservice
import (
"fmt"
"maunium.net/go/gomatrix"
)
type IntentAPI struct {
*gomatrix.Client
bot *gomatrix.Client
as *AppService
Localpart string
UserID string
}
func (as *AppService) NewIntentAPI(localpart string) *IntentAPI {
userID := fmt.Sprintf("@%s:%s", 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,
}
}
func (intent *IntentAPI) Register() error {
_, _, err := intent.Client.Register(&gomatrix.ReqRegister{
Username: intent.Localpart,
})
if err != nil {
return err
}
return nil
}
func (intent *IntentAPI) EnsureRegistered() error {
if intent.as.StateStore.IsRegistered(intent.UserID) {
return nil
}
err := intent.Register()
httpErr, ok := err.(gomatrix.HTTPError)
if !ok || httpErr.RespError.ErrCode != "M_USER_IN_USE" {
return err
}
intent.as.StateStore.MarkRegistered(intent.UserID)
return nil
}
func (intent *IntentAPI) EnsureJoined(roomID string) error {
if intent.as.StateStore.IsInRoom(roomID, intent.UserID) {
return nil
}
if err := intent.EnsureRegistered(); err != nil {
return err
}
resp, err := intent.JoinRoom(roomID, "", nil)
if err != nil {
httpErr, ok := err.(gomatrix.HTTPError)
if !ok || httpErr.RespError.ErrCode != "M_FORBIDDEN" || intent.bot == nil {
return httpErr
}
_, inviteErr := intent.bot.InviteUser(roomID, &gomatrix.ReqInviteUser{
UserID: intent.UserID,
})
if inviteErr != nil {
return err
}
resp, err = intent.JoinRoom(roomID, "", nil)
if err != nil {
return err
}
}
intent.as.StateStore.SetMembership(resp.RoomID, intent.UserID, "join")
return nil
}
func (intent *IntentAPI) SendMessageEvent(roomID string, eventType gomatrix.EventType, contentJSON interface{}) (*gomatrix.RespSendEvent, error) {
if err := intent.EnsureJoined(roomID); err != nil {
return nil, err
}
return intent.Client.SendMessageEvent(roomID, eventType, contentJSON)
}
func (intent *IntentAPI) SendMassagedMessageEvent(roomID string, eventType gomatrix.EventType, contentJSON interface{}, ts int64) (*gomatrix.RespSendEvent, error) {
if err := intent.EnsureJoined(roomID); err != nil {
return nil, err
}
return intent.Client.SendMassagedMessageEvent(roomID, eventType, contentJSON, ts)
}
func (intent *IntentAPI) SendStateEvent(roomID string, eventType gomatrix.EventType, stateKey string, contentJSON interface{}) (*gomatrix.RespSendEvent, error) {
if err := intent.EnsureJoined(roomID); err != nil {
return nil, err
}
return intent.Client.SendStateEvent(roomID, eventType, stateKey, contentJSON)
}
func (intent *IntentAPI) SendMassagedStateEvent(roomID string, eventType gomatrix.EventType, stateKey string, contentJSON interface{}, ts int64) (*gomatrix.RespSendEvent, error) {
if err := intent.EnsureJoined(roomID); err != nil {
return nil, err
}
return intent.Client.SendMassagedStateEvent(roomID, eventType, stateKey, contentJSON, ts)
}
func (intent *IntentAPI) StateEvent(roomID string, eventType gomatrix.EventType, stateKey string, outContent interface{}) (err error) {
if err := intent.EnsureJoined(roomID); err != nil {
return err
}
return intent.Client.StateEvent(roomID, eventType, stateKey, outContent)
}
func (intent *IntentAPI) PowerLevels(roomID string) (pl *gomatrix.PowerLevels, err error) {
pl = intent.as.StateStore.GetPowerLevels(roomID)
if pl == nil {
pl = &gomatrix.PowerLevels{}
err = intent.StateEvent(roomID, gomatrix.StatePowerLevels, "", pl)
if err == nil {
intent.as.StateStore.SetPowerLevels(roomID, pl)
}
}
return
}
func (intent *IntentAPI) SetPowerLevels(roomID string, levels *gomatrix.PowerLevels) (resp *gomatrix.RespSendEvent, err error) {
resp, err = intent.SendStateEvent(roomID, gomatrix.StatePowerLevels, "", &levels)
if err == nil {
intent.as.StateStore.SetPowerLevels(roomID, levels)
}
return
}
func (intent *IntentAPI) SetPowerLevel(roomID, userID string, level int) (*gomatrix.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, gomatrix.StatePowerLevels, "", &pl)
}
return nil, nil
}
func (intent *IntentAPI) SendText(roomID, text string) (*gomatrix.RespSendEvent, error) {
if err := intent.EnsureJoined(roomID); err != nil {
return nil, err
}
return intent.Client.SendText(roomID, text)
}
func (intent *IntentAPI) SendImage(roomID, body, url string) (*gomatrix.RespSendEvent, error) {
if err := intent.EnsureJoined(roomID); err != nil {
return nil, err
}
return intent.Client.SendImage(roomID, body, url)
}
func (intent *IntentAPI) SendVideo(roomID, body, url string) (*gomatrix.RespSendEvent, error) {
if err := intent.EnsureJoined(roomID); err != nil {
return nil, err
}
return intent.Client.SendVideo(roomID, body, url)
}
func (intent *IntentAPI) SendNotice(roomID, text string) (*gomatrix.RespSendEvent, error) {
if err := intent.EnsureJoined(roomID); err != nil {
return nil, err
}
return intent.Client.SendNotice(roomID, text)
}
func (intent *IntentAPI) RedactEvent(roomID, eventID string, req *gomatrix.ReqRedact) (*gomatrix.RespSendEvent, error) {
if err := intent.EnsureJoined(roomID); err != nil {
return nil, err
}
return intent.Client.RedactEvent(roomID, eventID, req)
}
func (intent *IntentAPI) SetRoomName(roomID, roomName string) (*gomatrix.RespSendEvent, error) {
return intent.SendStateEvent(roomID, "m.room.name", "", map[string]interface{}{
"name": roomName,
})
}
func (intent *IntentAPI) SetRoomAvatar(roomID, avatarURL string) (*gomatrix.RespSendEvent, error) {
return intent.SendStateEvent(roomID, "m.room.avatar", "", map[string]interface{}{
"url": avatarURL,
})
}
func (intent *IntentAPI) SetRoomTopic(roomID, topic string) (*gomatrix.RespSendEvent, error) {
return intent.SendStateEvent(roomID, "m.room.topic", "", map[string]interface{}{
"topic": topic,
})
}
func (intent *IntentAPI) SetDisplayName(displayName string) error {
if err := intent.EnsureRegistered(); err != nil {
return err
}
return intent.Client.SetDisplayName(displayName)
}
func (intent *IntentAPI) SetAvatarURL(avatarURL string) error {
if err := intent.EnsureRegistered(); err != nil {
return err
}
return intent.Client.SetAvatarURL(avatarURL)
}

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

@ -0,0 +1,59 @@
package appservice
import (
"encoding/json"
"net/http"
"maunium.net/go/gomatrix"
)
// EventList contains a list of events.
type EventList struct {
Events []*gomatrix.Event `json:"events"`
}
// EventListener is a function that receives events.
type EventListener func(event *gomatrix.Event)
// WriteBlankOK writes a blank OK message as a reply to a HTTP request.
func WriteBlankOK(w http.ResponseWriter) {
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 {
dataStr, err := json.Marshal(data)
if err != nil {
return err
}
_, err = w.Write([]byte(dataStr))
return err
}
// Error represents a Matrix protocol error.
type Error struct {
HTTPStatus int `json:"-"`
ErrorCode ErrorCode `json:"errcode"`
Message string `json:"message"`
}
func (err Error) Write(w http.ResponseWriter) {
w.WriteHeader(err.HTTPStatus)
Respond(w, &err)
}
// ErrorCode is the machine-readable code in an Error.
type ErrorCode string
// Native ErrorCodes
const (
ErrForbidden ErrorCode = "M_FORBIDDEN"
ErrUnknown ErrorCode = "M_UNKNOWN"
)
// Custom ErrorCodes
const (
ErrNoTransactionID ErrorCode = "NET.MAUNIUM.NO_TRANSACTION_ID"
ErrNoBody ErrorCode = "NET.MAUNIUM.NO_REQUEST_BODY"
ErrInvalidJSON ErrorCode = "NET.MAUNIUM.INVALID_JSON"
)

34
vendor/maunium.net/go/mautrix-appservice/random.go generated vendored Normal file
View File

@ -0,0 +1,34 @@
package appservice
import (
"math/rand"
"time"
)
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
const (
letterIdxBits = 6
letterIdxMask = 1<<letterIdxBits - 1
letterIdxMax = 63 / letterIdxBits
)
var src = rand.NewSource(time.Now().UnixNano())
// RandomString generates a random string of the given length.
func RandomString(n int) string {
b := make([]byte, n)
// A src.Int63() generates 63 random bits, enough for letterIdxMax characters!
for i, cache, remain := n-1, src.Int63(), letterIdxMax; i >= 0; {
if remain == 0 {
cache, remain = src.Int63(), letterIdxMax
}
if idx := int(cache & letterIdxMask); idx < len(letterBytes) {
b[i] = letterBytes[idx]
i--
}
cache >>= letterIdxBits
remain--
}
return string(b)
}

View File

@ -0,0 +1,99 @@
package appservice
import (
"io/ioutil"
"regexp"
"gopkg.in/yaml.v2"
)
// Registration contains the data in a Matrix appservice registration.
// See https://matrix.org/docs/spec/application_service/unstable.html#registration
type Registration struct {
ID string `yaml:"id"`
URL string `yaml:"url"`
AppToken string `yaml:"as_token"`
ServerToken string `yaml:"hs_token"`
SenderLocalpart string `yaml:"sender_localpart"`
RateLimited bool `yaml:"rate_limited"`
Namespaces Namespaces `yaml:"namespaces"`
}
// CreateRegistration creates a Registration with random appservice and homeserver tokens.
func CreateRegistration(name string) *Registration {
return &Registration{
AppToken: RandomString(64),
ServerToken: RandomString(64),
}
}
// LoadRegistration loads a YAML file and turns it into a Registration.
func LoadRegistration(path string) (*Registration, error) {
data, err := ioutil.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 ioutil.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 []Namespace `yaml:"users,omitempty"`
RoomAliases []Namespace `yaml:"aliases,omitempty"`
RoomIDs []Namespace `yaml:"rooms,omitempty"`
}
// Namespace is a reserved namespace in any area.
type Namespace struct {
Regex string `yaml:"regex"`
Exclusive bool `yaml:"exclusive"`
}
// RegisterUserIDs creates an user ID namespace registration.
func (nslist *Namespaces) RegisterUserIDs(regex *regexp.Regexp, exclusive bool) {
nslist.UserIDs = append(nslist.UserIDs, Namespace{
Regex: regex.String(),
Exclusive: exclusive,
})
}
// RegisterRoomAliases creates an room alias namespace registration.
func (nslist *Namespaces) RegisterRoomAliases(regex *regexp.Regexp, exclusive bool) {
nslist.RoomAliases = append(nslist.RoomAliases, Namespace{
Regex: regex.String(),
Exclusive: exclusive,
})
}
// RegisterRoomIDs creates an room ID namespace registration.
func (nslist *Namespaces) RegisterRoomIDs(regex *regexp.Regexp, exclusive bool) {
nslist.RoomIDs = append(nslist.RoomIDs, Namespace{
Regex: regex.String(),
Exclusive: exclusive,
})
}

132
vendor/maunium.net/go/mautrix-appservice/statestore.go generated vendored Normal file
View File

@ -0,0 +1,132 @@
package appservice
import (
"maunium.net/go/gomatrix"
"strings"
"sync"
)
type StateStore interface {
IsRegistered(userID string) bool
MarkRegistered(userID string)
IsInRoom(roomID, userID string) bool
SetMembership(roomID, userID, membership string)
SetPowerLevels(roomID string, levels *gomatrix.PowerLevels)
GetPowerLevels(roomID string) *gomatrix.PowerLevels
GetPowerLevel(roomID, userID string) int
GetPowerLevelRequirement(roomID string, eventType gomatrix.EventType, isState bool) int
HasPowerLevel(roomID, userID string, eventType gomatrix.EventType, isState bool) bool
}
func (as *AppService) UpdateState(evt *gomatrix.Event) {
switch evt.Type {
case gomatrix.StateMember:
as.StateStore.SetMembership(evt.RoomID, evt.GetStateKey(), evt.Content.Membership)
}
}
type BasicStateStore struct {
registrationsLock sync.RWMutex `json:"-"`
Registrations map[string]bool `json:"registrations"`
membershipsLock sync.RWMutex `json:"-"`
Memberships map[string]map[string]string `json:"memberships"`
powerLevelsLock sync.RWMutex `json:"-"`
PowerLevels map[string]*gomatrix.PowerLevels `json:"power_levels"`
}
func NewBasicStateStore() *BasicStateStore {
return &BasicStateStore{
Registrations: make(map[string]bool),
Memberships: make(map[string]map[string]string),
PowerLevels: make(map[string]*gomatrix.PowerLevels),
}
}
func (store *BasicStateStore) IsRegistered(userID string) bool {
store.registrationsLock.RLock()
registered, ok := store.Registrations[userID]
store.registrationsLock.RUnlock()
return ok && registered
}
func (store *BasicStateStore) MarkRegistered(userID string) {
store.registrationsLock.Lock()
store.Registrations[userID] = true
store.registrationsLock.Unlock()
}
func (store *BasicStateStore) GetRoomMemberships(roomID string) map[string]string {
store.membershipsLock.RLock()
memberships, ok := store.Memberships[roomID]
store.membershipsLock.RUnlock()
if !ok {
memberships = make(map[string]string)
store.membershipsLock.Lock()
store.Memberships[roomID] = memberships
store.membershipsLock.Unlock()
}
return memberships
}
func (store *BasicStateStore) GetMembership(roomID, userID string) string {
store.membershipsLock.RLock()
membership, ok := store.GetRoomMemberships(roomID)[userID]
store.membershipsLock.RUnlock()
if !ok {
return "leave"
}
return membership
}
func (store *BasicStateStore) IsInRoom(roomID, userID string) bool {
return store.GetMembership(roomID, userID) == "join"
}
func (store *BasicStateStore) SetMembership(roomID, userID, membership string) {
store.membershipsLock.Lock()
memberships, ok := store.Memberships[roomID]
if !ok {
store.Memberships[roomID] = map[string]string{
userID: strings.ToLower(membership),
}
} else {
memberships[userID] = strings.ToLower(membership)
}
store.membershipsLock.Unlock()
}
func (store *BasicStateStore) SetPowerLevels(roomID string, levels *gomatrix.PowerLevels) {
store.powerLevelsLock.Lock()
store.PowerLevels[roomID] = levels
store.powerLevelsLock.Unlock()
}
func (store *BasicStateStore) GetPowerLevels(roomID string) (levels *gomatrix.PowerLevels) {
store.powerLevelsLock.RLock()
levels, _ = store.PowerLevels[roomID]
store.powerLevelsLock.RUnlock()
return
}
func (store *BasicStateStore) GetPowerLevel(roomID, userID string) int {
return store.GetPowerLevels(roomID).GetUserLevel(userID)
}
func (store *BasicStateStore) GetPowerLevelRequirement(roomID string, eventType gomatrix.EventType, isState bool) int {
levels := store.GetPowerLevels(roomID)
switch eventType {
case "kick":
return levels.Kick()
case "invite":
return levels.Invite()
case "redact":
return levels.Redact()
}
return levels.GetEventLevel(eventType, isState)
}
func (store *BasicStateStore) HasPowerLevel(roomID, userID string, eventType gomatrix.EventType, isState bool) bool {
return store.GetPowerLevel(roomID, userID) >= store.GetPowerLevelRequirement(roomID, eventType, isState)
}