commit 0eec21a213050bfd6c617a29e4be12b93e5eb427
Author: Sebastian P <5564491+s3lph@users.noreply.github.com>
Date: Thu Oct 01 22:50:56 2020 +0000
diff --git a/README.md b/README.md
index 66ce3b6..c38e205 100644
--- a/README.md
+++ b/README.md
@@ -936 +937 @@ And more...
- [Matrix](https://matrix.org)
- [Mattermost](https://github.com/mattermost/mattermost-server/) 4.x, 5.x
- [Microsoft Teams](https://teams.microsoft.com)
+- [Mumble](https://www.mumble.info/)
- [Nextcloud Talk](https://nextcloud.com/talk/)
- [Rocket.chat](https://rocket.chat)
- [Slack](https://slack.com)
@@ -3246 +3257 @@ Matterbridge wouldn't exist without these libraries:
- gitter - <https://github.com/sromku/go-gitter>
- gops - <https://github.com/google/gops>
- gozulipbot - <https://github.com/ifo/gozulipbot>
+- gumble - <https://github.com/layeh/gumble>
- irc - <https://github.com/lrstanley/girc>
- keybase - <https://github.com/keybase/go-keybase-chat-bot>
- matrix - <https://github.com/matrix-org/gomatrix>
diff --git a/bridge/config/config.go b/bridge/config/config.go
index f34da51..a1bce8d 100644
--- a/bridge/config/config.go
+++ b/bridge/config/config.go
@@ -2136 +2137 @@ type BridgeValues struct {
WhatsApp map[string]Protocol // TODO is this struct used? Search for "SlackLegacy" for example didn't return any results
Zulip map[string]Protocol
Keybase map[string]Protocol
+ Mumble map[string]Protocol
General Protocol
Tengo Tengo
Gateway []Gateway
diff --git a/bridge/mumble/handlers.go b/bridge/mumble/handlers.go
new file mode 100644
index 0000000..a684595
--- /dev/null
+++ b/bridge/mumble/handlers.go
@@ -00 +190 @@
+package bmumble
+
+import (
+ "strconv"
+ "time"
+
+ "layeh.com/gumble/gumble"
+
+ "github.com/42wim/matterbridge/bridge/config"
+ "github.com/42wim/matterbridge/bridge/helper"
+)
+
+func (b *Bmumble) handleServerConfig(event *gumble.ServerConfigEvent) {
+ b.serverConfigUpdate <- *event
+}
+
+func (b *Bmumble) handleTextMessage(event *gumble.TextMessageEvent) {
+ sender := "unknown"
+ if event.TextMessage.Sender != nil {
+ sender = event.TextMessage.Sender.Name
+ }
+ // Convert Mumble HTML messages to markdown
+ parts, err := b.convertHTMLtoMarkdown(event.TextMessage.Message)
+ if err != nil {
+ b.Log.Error(err)
+ }
+ now := time.Now().UTC()
+ for i, part := range parts {
+ // Construct matterbridge message and pass on to the gateway
+ rmsg := config.Message{
+ Channel: strconv.FormatUint(uint64(event.Client.Self.Channel.ID), 10),
+ Username: sender,
+ UserID: sender + "@" + b.Host,
+ Account: b.Account,
+ }
+ if part.Image == nil {
+ rmsg.Text = part.Text
+ } else {
+ fname := b.Account + "_" + strconv.FormatInt(now.UnixNano(), 10) + "_" + strconv.Itoa(i) + part.FileExtension
+ rmsg.Extra = make(map[string][]interface{})
+ if err = helper.HandleDownloadSize(b.Log, &rmsg, fname, int64(len(part.Image)), b.General); err != nil {
+ b.Log.WithError(err).Warn("not including image in message")
+ continue
+ }
+ helper.HandleDownloadData(b.Log, &rmsg, fname, "", "", &part.Image, b.General)
+ }
+ b.Log.Debugf("Sending message to gateway: %+v", rmsg)
+ b.Remote <- rmsg
+ }
+}
+
+func (b *Bmumble) handleConnect(event *gumble.ConnectEvent) {
+ // Set the user's "bio"/comment
+ if comment := b.GetString("UserComment"); comment != "" && event.Client.Self != nil {
+ event.Client.Self.SetComment(comment)
+ }
+ // No need to talk or listen
+ event.Client.Self.SetSelfDeafened(true)
+ event.Client.Self.SetSelfMuted(true)
+ // if the Channel variable is set, this is a reconnect -> rejoin channel
+ if b.Channel != nil {
+ if err := b.doJoin(event.Client, *b.Channel); err != nil {
+ b.Log.Error(err)
+ }
+ b.Remote <- config.Message{
+ Username: "system",
+ Text: "rejoin",
+ Channel: "",
+ Account: b.Account,
+ Event: config.EventRejoinChannels,
+ }
+ }
+}
+
+func (b *Bmumble) handleUserChange(event *gumble.UserChangeEvent) {
+ // Only care about changes to self
+ if event.User != event.Client.Self {
+ return
+ }
+ // Someone attempted to move the user out of the configured channel; attempt to join back
+ if b.Channel != nil {
+ if err := b.doJoin(event.Client, *b.Channel); err != nil {
+ b.Log.Error(err)
+ }
+ }
+}
+
+func (b *Bmumble) handleDisconnect(event *gumble.DisconnectEvent) {
+ b.connected <- *event
+}
diff --git a/bridge/mumble/helpers.go b/bridge/mumble/helpers.go
new file mode 100644
index 0000000..c828df2
--- /dev/null
+++ b/bridge/mumble/helpers.go
@@ -00 +1143 @@
+package bmumble
+
+import (
+ "fmt"
+ "mime"
+ "net/http"
+ "regexp"
+ "strings"
+
+ "github.com/42wim/matterbridge/bridge/config"
+ "github.com/mattn/godown"
+ "github.com/vincent-petithory/dataurl"
+)
+
+type MessagePart struct {
+ Text string
+ FileExtension string
+ Image []byte
+}
+
+func (b *Bmumble) decodeImage(uri string, parts *[]MessagePart) error {
+ // Decode the data:image/... URI
+ image, err := dataurl.DecodeString(uri)
+ if err != nil {
+ b.Log.WithError(err).Info("No image extracted")
+ return err
+ }
+ // Determine the file extensions for that image
+ ext, err := mime.ExtensionsByType(image.MediaType.ContentType())
+ if err != nil || len(ext) == 0 {
+ b.Log.WithError(err).Infof("No file extension registered for MIME type '%s'", image.MediaType.ContentType())
+ return err
+ }
+ // Add the image to the MessagePart slice
+ *parts = append(*parts, MessagePart{"", ext[0], image.Data})
+ return nil
+}
+
+func (b *Bmumble) tokenize(t *string) ([]MessagePart, error) {
+ // `^(.*?)` matches everything before the image
+ // `!\[[^\]]*\]\(` matches the `]+)` matches the data: URI used by Mumble
+ // `\)` matches the closing parenthesis after the URI
+ // `(.*)$` matches the remaining text to be examined in the next iteration
+ p := regexp.MustCompile(`^(?ms)(.*?)!\[[^\]]*\]\((data:image\/[^)]+)\)(.*)$`)
+ remaining := *t
+ var parts []MessagePart
+ for {
+ tokens := p.FindStringSubmatch(remaining)
+ if tokens == nil {
+ // no match -> remaining string is non-image text
+ pre := strings.TrimSpace(remaining)
+ if len(pre) > 0 {
+ parts = append(parts, MessagePart{pre, "", nil})
+ }
+ return parts, nil
+ }
+
+ // tokens[1] is the text before the image
+ if len(tokens[1]) > 0 {
+ pre := strings.TrimSpace(tokens[1])
+ parts = append(parts, MessagePart{pre, "", nil})
+ }
+ // tokens[2] is the image URL
+ uri, err := dataurl.UnescapeToString(strings.TrimSpace(strings.ReplaceAll(tokens[2], " ", "")))
+ if err != nil {
+ b.Log.WithError(err).Info("URL unescaping failed")
+ remaining = strings.TrimSpace(tokens[3])
+ continue
+ }
+ err = b.decodeImage(uri, &parts)
+ if err != nil {
+ b.Log.WithError(err).Info("Decoding the image failed")
+ }
+ // tokens[3] is the text after the image, processed in the next iteration
+ remaining = strings.TrimSpace(tokens[3])
+ }
+}
+
+func (b *Bmumble) convertHTMLtoMarkdown(html string) ([]MessagePart, error) {
+ var sb strings.Builder
+ err := godown.Convert(&sb, strings.NewReader(html), nil)
+ if err != nil {
+ return nil, err
+ }
+ markdown := sb.String()
+ b.Log.Debugf("### to markdown: %s", markdown)
+ return b.tokenize(&markdown)
+}
+
+func (b *Bmumble) extractFiles(msg *config.Message) []config.Message {
+ var messages []config.Message
+ if msg.Extra == nil || len(msg.Extra["file"]) == 0 {
+ return messages
+ }
+ // Create a separate message for each file
+ for _, f := range msg.Extra["file"] {
+ fi := f.(config.FileInfo)
+ imsg := config.Message{
+ Channel: msg.Channel,
+ Username: msg.Username,
+ UserID: msg.UserID,
+ Account: msg.Account,
+ Protocol: msg.Protocol,
+ Timestamp: msg.Timestamp,
+ Event: "mumble_image",
+ }
+ // If no data is present for the file, send a link instead
+ if fi.Data == nil || len(*fi.Data) == 0 {
+ if len(fi.URL) > 0 {
+ imsg.Text = fmt.Sprintf(`<a href="%s">%s</a>`, fi.URL, fi.URL)
+ messages = append(messages, imsg)
+ } else {
+ b.Log.Infof("Not forwarding file without local data")
+ }
+ continue
+ }
+ mimeType := http.DetectContentType(*fi.Data)
+ // Mumble only supports images natively, send a link instead
+ if !strings.HasPrefix(mimeType, "image/") {
+ if len(fi.URL) > 0 {
+ imsg.Text = fmt.Sprintf(`<a href="%s">%s</a>`, fi.URL, fi.URL)
+ messages = append(messages, imsg)
+ } else {
+ b.Log.Infof("Not forwarding file of type %s", mimeType)
+ }
+ continue
+ }
+ mimeType = strings.TrimSpace(strings.Split(mimeType, ";")[0])
+ // Build data:image/...;base64,... style image URL and embed image directly into the message
+ du := dataurl.New(*fi.Data, mimeType)
+ dataURL, err := du.MarshalText()
+ if err != nil {
+ b.Log.WithError(err).Infof("Image Serialization into data URL failed (type: %s, length: %d)", mimeType, len(*fi.Data))
+ continue
+ }
+ imsg.Text = fmt.Sprintf(`<img src="%s"/>`, dataURL)
+ messages = append(messages, imsg)
+ }
+ // Remove files from original message
+ msg.Extra["file"] = nil
+ return messages
+}
diff --git a/bridge/mumble/mumble.go b/bridge/mumble/mumble.go
new file mode 100644
index 0000000..2281d1c
--- /dev/null
+++ b/bridge/mumble/mumble.go
@@ -00 +1259 @@
+package bmumble
+
+import (
+ "crypto/tls"
+ "crypto/x509"
+ "errors"
+ "fmt"
+ "io/ioutil"
+ "net"
+ "strconv"
+ "time"
+
+ "layeh.com/gumble/gumble"
+ "layeh.com/gumble/gumbleutil"
+
+ "github.com/42wim/matterbridge/bridge"
+ "github.com/42wim/matterbridge/bridge/config"
+ "github.com/42wim/matterbridge/bridge/helper"
+ stripmd "github.com/writeas/go-strip-markdown"
+
+ // We need to import the 'data' package as an implicit dependency.
+ // See: https://godoc.org/github.com/paulrosania/go-charset/charset
+ _ "github.com/paulrosania/go-charset/data"
+)
+
+type Bmumble struct {
+ client *gumble.Client
+ Nick string
+ Host string
+ Channel *uint32
+ local chan config.Message
+ running chan error
+ connected chan gumble.DisconnectEvent
+ serverConfigUpdate chan gumble.ServerConfigEvent
+ serverConfig gumble.ServerConfigEvent
+ tlsConfig tls.Config
+
+ *bridge.Config
+}
+
+func New(cfg *bridge.Config) bridge.Bridger {
+ b := &Bmumble{}
+ b.Config = cfg
+ b.Nick = b.GetString("Nick")
+ b.local = make(chan config.Message)
+ b.running = make(chan error)
+ b.connected = make(chan gumble.DisconnectEvent)
+ b.serverConfigUpdate = make(chan gumble.ServerConfigEvent)
+ return b
+}
+
+func (b *Bmumble) Connect() error {
+ b.Log.Infof("Connecting %s", b.GetString("Server"))
+ host, portstr, err := net.SplitHostPort(b.GetString("Server"))
+ if err != nil {
+ return err
+ }
+ b.Host = host
+ _, err = strconv.Atoi(portstr)
+ if err != nil {
+ return err
+ }
+
+ if err = b.buildTLSConfig(); err != nil {
+ return err
+ }
+
+ go b.doSend()
+ go b.connectLoop()
+ err = <-b.running
+ return err
+}
+
+func (b *Bmumble) Disconnect() error {
+ return b.client.Disconnect()
+}
+
+func (b *Bmumble) JoinChannel(channel config.ChannelInfo) error {
+ cid, err := strconv.ParseUint(channel.Name, 10, 32)
+ if err != nil {
+ return err
+ }
+ channelID := uint32(cid)
+ if b.Channel != nil && *b.Channel != channelID {
+ b.Log.Fatalf("Cannot join channel ID '%d', already joined to channel ID %d", channelID, *b.Channel)
+ return errors.New("the Mumble bridge can only join a single channel")
+ }
+ b.Channel = &channelID
+ return b.doJoin(b.client, channelID)
+}
+
+func (b *Bmumble) Send(msg config.Message) (string, error) {
+ // Only process text messages
+ b.Log.Debugf("=> Received local message %#v", msg)
+ if msg.Event != "" && msg.Event != config.EventUserAction {
+ return "", nil
+ }
+
+ attachments := b.extractFiles(&msg)
+ b.local <- msg
+ for _, a := range attachments {
+ b.local <- a
+ }
+ return "", nil
+}
+
+func (b *Bmumble) buildTLSConfig() error {
+ b.tlsConfig = tls.Config{}
+ // Load TLS client certificate keypair required for registered user authentication
+ if cpath := b.GetString("TLSClientCertificate"); cpath != "" {
+ if ckey := b.GetString("TLSClientKey"); ckey != "" {
+ cert, err := tls.LoadX509KeyPair(cpath, ckey)
+ if err != nil {
+ return err
+ }
+ b.tlsConfig.Certificates = []tls.Certificate{cert}
+ }
+ }
+ // Load TLS CA used for server verification. If not provided, the Go system trust anchor is used
+ if capath := b.GetString("TLSCACertificate"); capath != "" {
+ ca, err := ioutil.ReadFile(capath)
+ if err != nil {
+ return err
+ }
+ b.tlsConfig.RootCAs = x509.NewCertPool()
+ b.tlsConfig.RootCAs.AppendCertsFromPEM(ca)
+ }
+ b.tlsConfig.InsecureSkipVerify = b.GetBool("SkipTLSVerify")
+ return nil
+}
+
+func (b *Bmumble) connectLoop() {
+ firstConnect := true
+ for {
+ err := b.doConnect()
+ if firstConnect {
+ b.running <- err
+ }
+ if err != nil {
+ b.Log.Errorf("Connection to server failed: %#v", err)
+ if firstConnect {
+ break
+ } else {
+ b.Log.Info("Retrying in 10s")
+ time.Sleep(10 * time.Second)
+ continue
+ }
+ }
+ firstConnect = false
+ d := <-b.connected
+ switch d.Type {
+ case gumble.DisconnectError:
+ b.Log.Errorf("Lost connection to the server (%s), attempting reconnect", d.String)
+ continue
+ case gumble.DisconnectKicked:
+ b.Log.Errorf("Kicked from the server (%s), attempting reconnect", d.String)
+ continue
+ case gumble.DisconnectBanned:
+ b.Log.Errorf("Banned from the server (%s), not attempting reconnect", d.String)
+ close(b.connected)
+ close(b.running)
+ return
+ case gumble.DisconnectUser:
+ b.Log.Infof("Disconnect successful")
+ close(b.connected)
+ close(b.running)
+ return
+ }
+ }
+}
+
+func (b *Bmumble) doConnect() error {
+ // Create new gumble config and attach event handlers
+ gumbleConfig := gumble.NewConfig()
+ gumbleConfig.Attach(gumbleutil.Listener{
+ ServerConfig: b.handleServerConfig,
+ TextMessage: b.handleTextMessage,
+ Connect: b.handleConnect,
+ Disconnect: b.handleDisconnect,
+ UserChange: b.handleUserChange,
+ })
+ gumbleConfig.Username = b.GetString("Nick")
+ if password := b.GetString("Password"); password != "" {
+ gumbleConfig.Password = password
+ }
+
+ client, err := gumble.DialWithDialer(new(net.Dialer), b.GetString("Server"), gumbleConfig, &b.tlsConfig)
+ if err != nil {
+ return err
+ }
+ b.client = client
+ return nil
+}
+
+func (b *Bmumble) doJoin(client *gumble.Client, channelID uint32) error {
+ channel, ok := client.Channels[channelID]
+ if !ok {
+ return fmt.Errorf("no channel with ID %d", channelID)
+ }
+ client.Self.Move(channel)
+ return nil
+}
+
+func (b *Bmumble) doSend() {
+ // Message sending loop that makes sure server-side
+ // restrictions and client-side message traits don't conflict
+ // with each other.
+ for {
+ select {
+ case serverConfig := <-b.serverConfigUpdate:
+ b.Log.Debugf("Received server config update: AllowHTML=%#v, MaximumMessageLength=%#v", serverConfig.AllowHTML, serverConfig.MaximumMessageLength)
+ b.serverConfig = serverConfig
+ case msg := <-b.local:
+ b.processMessage(&msg)
+ }
+ }
+}
+
+func (b *Bmumble) processMessage(msg *config.Message) {
+ b.Log.Debugf("Processing message %s", msg.Text)
+
+ allowHTML := true
+ if b.serverConfig.AllowHTML != nil {
+ allowHTML = *b.serverConfig.AllowHTML
+ }
+
+ // If this is a specially generated image message, send it unmodified
+ if msg.Event == "mumble_image" {
+ if allowHTML {
+ b.client.Self.Channel.Send(msg.Username+msg.Text, false)
+ } else {
+ b.Log.Info("Can't send image, server does not allow HTML messages")
+ }
+ return
+ }
+
+ // Don't process empty messages
+ if len(msg.Text) == 0 {
+ return
+ }
+ // If HTML is allowed, convert markdown into HTML, otherwise strip markdown
+ if allowHTML {
+ msg.Text = helper.ParseMarkdown(msg.Text)
+ } else {
+ msg.Text = stripmd.Strip(msg.Text)
+ }
+
+ // If there is a maximum message length, split and truncate the lines
+ var msgLines []string
+ if maxLength := b.serverConfig.MaximumMessageLength; maxLength != nil {
+ msgLines = helper.GetSubLines(msg.Text, *maxLength-len(msg.Username))
+ } else {
+ msgLines = helper.GetSubLines(msg.Text, 0)
+ }
+ // Send the individual lindes
+ for i := range msgLines {
+ b.client.Self.Channel.Send(msg.Username+msgLines[i], false)
+ }
+}
diff --git a/gateway/bridgemap/bmumble.go b/gateway/bridgemap/bmumble.go
new file mode 100644
index 0000000..7b9241f
--- /dev/null
+++ b/gateway/bridgemap/bmumble.go
@@ -00 +111 @@
+// +build !nomumble
+
+package bridgemap
+
+import (
+ bmumble "github.com/42wim/matterbridge/bridge/mumble"
+)
+
+func init() {
+ FullMap["mumble"] = bmumble.New
+}
diff --git a/go.mod b/go.mod
index f0c244d..bf5513a 100644
--- a/go.mod
+++ b/go.mod
@@ -436 +437 @@ require (
github.com/slack-go/slack v0.6.6
github.com/spf13/viper v1.7.1
github.com/stretchr/testify v1.6.1
+ github.com/vincent-petithory/dataurl v0.0.0-20191104211930-d1553a71de50
github.com/writeas/go-strip-markdown v2.0.1+incompatible
github.com/x-cray/logrus-prefixed-formatter v0.5.2 // indirect
github.com/yaegashi/msgraph.go v0.1.4
@@ -516 +527 @@ require (
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43
gomod.garykim.dev/nc-talk v0.1.3
gopkg.in/olahol/melody.v1 v1.0.0-20170518105555-d52139073376
+ layeh.com/gumble v0.0.0-20200818122324-146f9205029b
)
go 1.13
diff --git a/go.sum b/go.sum
index 97cb5b8..bda19a2 100644
--- a/go.sum
+++ b/go.sum
@@ -1456 +1457 @@ github.com/d5/tengo/v2 v2.6.0/go.mod h1:XRGjEs5I9jYIKTxly6HCF8oiiilk5E/RYXOZ5b0D
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/dchote/go-openal v0.0.0-20171116030048-f4a9a141d372/go.mod h1:74z+CYu2/mx4N+mcIS/rsvfAxBPBV9uv8zRAnwyFkdI=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/dgoogauth v0.0.0-20190221195224-5a805980a5f3/go.mod h1:hEfFauPHz7+NnjR/yHJGhrKo1Za+zStgwUETx3yzqgY=
@@ -6916 +6928 @@ github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=
+github.com/vincent-petithory/dataurl v0.0.0-20191104211930-d1553a71de50 h1:uxE3GYdXIOfhMv3unJKETJEhw78gvzuQqRX/rVirc2A=
+github.com/vincent-petithory/dataurl v0.0.0-20191104211930-d1553a71de50/go.mod h1:FHafX5vmDzyP+1CQATJn7WFKc9CvnvxyvZy6I1MrG/U=
github.com/wiggin77/cfg v1.0.2/go.mod h1:b3gotba2e5bXTqTW48DwIFoLc+4lWKP7WPi/CdvZ4aE=
github.com/wiggin77/logr v1.0.4/go.mod h1:h98FF6GPfThhDrHCg063hZA1sIyOEzQ/P85wgqI0IqE=
github.com/wiggin77/merror v1.0.2/go.mod h1:uQTcIU0Z6jRK4OwqganPYerzQxSFJ4GSHM3aurxxQpg=
@@ -11346 +11379 @@ honnef.co/go/tools v0.0.1-2020.1.3 h1:sXmLre5bzIR6ypkjXCDI3jHPssRhc8KD/Ome589sc3
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4 h1:UoveltGrhghAA7ePc+e+QYDHXrBps2PqFZiHkGR/xK8=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
+layeh.com/gopus v0.0.0-20161224163843-0ebf989153aa/go.mod h1:AOef7vHz0+v4sWwJnr0jSyHiX/1NgsMoaxl+rEPz/I0=
+layeh.com/gumble v0.0.0-20200818122324-146f9205029b h1:Kne6wkHqbqrygRsqs5XUNhSs84DFG5TYMeCkCbM56sY=
+layeh.com/gumble v0.0.0-20200818122324-146f9205029b/go.mod h1:tWPVA9ZAfImNwabjcd9uDE+Mtz0Hfs7a7G3vxrnrwyc=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/goversion v1.2.0 h1:SPn+NLTiAG7w30IRK/DKp1BjvpWabYgxlLp/+kx5J8w=
rsc.io/goversion v1.2.0/go.mod h1:Eih9y/uIBS3ulggl7KNJ09xGSLcuNaLgmvvqa07sgfo=
diff --git a/matterbridge.toml.sample b/matterbridge.toml.sample
index b1574f3..c9263e7 100644
--- a/matterbridge.toml.sample
+++ b/matterbridge.toml.sample
@@ -14056 +140552 @@ Login = "talkuser"
# Password of the bot
Password = "talkuserpass"
+###################################################################
+#
+# Mumble
+#
+###################################################################
+
+[mumble.bridge]
+
+# Host and port of your Mumble server
+Server = "mumble.yourdomain.me:64738"
+
+# Nickname to log in as
+Nick = "matterbridge"
+
+# Some servers require a password
+# OPTIONAL (default empty)
+Password = "serverpasswordhere"
+
+# User comment to set on the Mumble user, visible to other users.
+# OPTIONAL (default empty)
+UserComment="I am bridging text messages between this channel and #general on irc.yourdomain.me"
+
+# Self-signed TLS client certificate + private key used to connect to
+# Mumble. This is required if you want to register the matterbridge
+# user on your Mumble server, so its nick becomes reserved.
+# You can generate a keypair using e.g.
+#
+# openssl req -x509 -newkey rsa:2048 -nodes -days 10000 \
+# -keyout mumble.key -out mumble.crt
+#
+# To actually register the matterbridege user, connect to Mumble as an
+# admin, right click on the user and click "Register".
+#
+# OPTIONAL (default empty)
+TLSClientCertificate="mumble.crt"
+TLSClientKey="mumble.key"
+
+# TLS CA certificate used to validate the Mumble server.
+# OPTIONAL (defaults to Go system CA)
+TLSCACertificate=mumble-ca.crt
+
+# Enable to not verify the certificate on your Mumble server.
+# e.g. when using selfsigned certificates
+# OPTIONAL (default false)
+SkipTLSVerify=false
+
###################################################################
#
# WhatsApp
@@ -17456 +17918 @@ enable=true
+ # mumble | channel id | 42 | The channel ID, as shown in the channel's "Edit" window
+ # -------------------------------------------------------------------------------------------------------------------------------------