mirror of
https://github.com/tulir/mautrix-whatsapp
synced 2024-11-11 20:42:39 +01:00
Switch to go-yaml v3 and add config updater. Fixes #243
This commit is contained in:
parent
19104d857b
commit
465fa4aa16
8 changed files with 527 additions and 75 deletions
|
@ -54,7 +54,6 @@ type BridgeConfig struct {
|
||||||
DoublePuppetServerMap map[string]string `yaml:"double_puppet_server_map"`
|
DoublePuppetServerMap map[string]string `yaml:"double_puppet_server_map"`
|
||||||
DoublePuppetAllowDiscovery bool `yaml:"double_puppet_allow_discovery"`
|
DoublePuppetAllowDiscovery bool `yaml:"double_puppet_allow_discovery"`
|
||||||
LoginSharedSecretMap map[string]string `yaml:"login_shared_secret_map"`
|
LoginSharedSecretMap map[string]string `yaml:"login_shared_secret_map"`
|
||||||
LegacyLoginSharedSecret string `yaml:"login_shared_secret"`
|
|
||||||
|
|
||||||
PrivateChatPortalMeta bool `yaml:"private_chat_portal_meta"`
|
PrivateChatPortalMeta bool `yaml:"private_chat_portal_meta"`
|
||||||
BridgeNotices bool `yaml:"bridge_notices"`
|
BridgeNotices bool `yaml:"bridge_notices"`
|
||||||
|
@ -65,12 +64,9 @@ type BridgeConfig struct {
|
||||||
TagOnlyOnCreate bool `yaml:"tag_only_on_create"`
|
TagOnlyOnCreate bool `yaml:"tag_only_on_create"`
|
||||||
MarkReadOnlyOnCreate bool `yaml:"mark_read_only_on_create"`
|
MarkReadOnlyOnCreate bool `yaml:"mark_read_only_on_create"`
|
||||||
EnableStatusBroadcast bool `yaml:"enable_status_broadcast"`
|
EnableStatusBroadcast bool `yaml:"enable_status_broadcast"`
|
||||||
|
WhatsappThumbnail bool `yaml:"whatsapp_thumbnail"`
|
||||||
WhatsappThumbnail bool `yaml:"whatsapp_thumbnail"`
|
AllowUserInvite bool `yaml:"allow_user_invite"`
|
||||||
|
FederateRooms bool `yaml:"federate_rooms"`
|
||||||
AllowUserInvite bool `yaml:"allow_user_invite"`
|
|
||||||
|
|
||||||
FederateRooms bool `yaml:"federate_rooms"`
|
|
||||||
|
|
||||||
CommandPrefix string `yaml:"command_prefix"`
|
CommandPrefix string `yaml:"command_prefix"`
|
||||||
|
|
||||||
|
|
|
@ -18,13 +18,11 @@ package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
|
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v3"
|
||||||
|
|
||||||
"maunium.net/go/mautrix/id"
|
|
||||||
|
|
||||||
"maunium.net/go/mautrix/appservice"
|
"maunium.net/go/mautrix/appservice"
|
||||||
|
"maunium.net/go/mautrix/id"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ExampleConfig string
|
var ExampleConfig string
|
||||||
|
@ -99,37 +97,23 @@ func (config *Config) CanDoublePuppetBackfill(userID id.UserID) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func Load(path string) (*Config, error) {
|
func Load(data []byte, upgraded bool) (*Config, error) {
|
||||||
data, err := os.ReadFile(path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var config = &Config{}
|
var config = &Config{}
|
||||||
err = yaml.UnmarshalStrict([]byte(ExampleConfig), config)
|
if !upgraded {
|
||||||
if err != nil {
|
// Fallback: if config upgrading failed, load example config for base values
|
||||||
return nil, fmt.Errorf("failed to unmarshal example config: %w", err)
|
err := yaml.Unmarshal([]byte(ExampleConfig), config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to unmarshal example config: %w", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
err = yaml.Unmarshal(data, config)
|
err := yaml.Unmarshal(data, config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(config.Bridge.LegacyLoginSharedSecret) > 0 {
|
|
||||||
config.Bridge.LoginSharedSecretMap[config.Homeserver.Domain] = config.Bridge.LegacyLoginSharedSecret
|
|
||||||
}
|
|
||||||
|
|
||||||
return config, err
|
return config, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (config *Config) Save(path string) error {
|
|
||||||
data, err := yaml.Marshal(config)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return os.WriteFile(path, data, 0600)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (config *Config) MakeAppService() (*appservice.AppService, error) {
|
func (config *Config) MakeAppService() (*appservice.AppService, error) {
|
||||||
as := appservice.Create()
|
as := appservice.Create()
|
||||||
as.HomeserverDomain = config.Homeserver.Domain
|
as.HomeserverDomain = config.Homeserver.Domain
|
||||||
|
|
176
config/upgrade.go
Normal file
176
config/upgrade.go
Normal file
|
@ -0,0 +1,176 @@
|
||||||
|
// mautrix-whatsapp - A Matrix-WhatsApp puppeting bridge.
|
||||||
|
// Copyright (C) 2021 Tulir Asokan
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
|
||||||
|
"maunium.net/go/mautrix/appservice"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (helper *UpgradeHelper) TempMethod() {
|
||||||
|
helper.doUpgrade()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (helper *UpgradeHelper) doUpgrade() {
|
||||||
|
helper.Copy(Str, "homeserver", "address")
|
||||||
|
helper.Copy(Str, "homeserver", "domain")
|
||||||
|
helper.Copy(Bool, "homeserver", "asmux")
|
||||||
|
helper.Copy(Str|Null, "homeserver", "status_endpoint")
|
||||||
|
|
||||||
|
helper.Copy(Str, "appservice", "address")
|
||||||
|
helper.Copy(Str, "appservice", "hostname")
|
||||||
|
helper.Copy(Int, "appservice", "port")
|
||||||
|
helper.Copy(Str, "appservice", "database", "type")
|
||||||
|
helper.Copy(Str, "appservice", "database", "uri")
|
||||||
|
helper.Copy(Int, "appservice", "database", "max_open_conns")
|
||||||
|
helper.Copy(Int, "appservice", "database", "max_idle_conns")
|
||||||
|
helper.Copy(Str, "appservice", "provisioning", "prefix")
|
||||||
|
if secret, ok := helper.Get(Str, "appservice", "provisioning", "shared_secret"); !ok || secret == "generate" {
|
||||||
|
sharedSecret := appservice.RandomString(64)
|
||||||
|
helper.Set(Str, sharedSecret, "appservice", "provisioning", "shared_secret")
|
||||||
|
} else {
|
||||||
|
helper.Copy(Str, "appservice", "provisioning", "shared_secret")
|
||||||
|
}
|
||||||
|
helper.Copy(Str, "appservice", "id")
|
||||||
|
helper.Copy(Str, "appservice", "bot", "username")
|
||||||
|
helper.Copy(Str, "appservice", "bot", "displayname")
|
||||||
|
helper.Copy(Str, "appservice", "bot", "avatar")
|
||||||
|
helper.Copy(Str, "appservice", "as_token")
|
||||||
|
helper.Copy(Str, "appservice", "hs_token")
|
||||||
|
|
||||||
|
helper.Copy(Bool, "metrics", "enabled")
|
||||||
|
helper.Copy(Str, "metrics", "listen")
|
||||||
|
|
||||||
|
helper.Copy(Str, "whatsapp", "os_name")
|
||||||
|
helper.Copy(Str, "whatsapp", "browser_name")
|
||||||
|
|
||||||
|
helper.Copy(Str, "bridge", "username_template")
|
||||||
|
helper.Copy(Str, "bridge", "displayname_template")
|
||||||
|
helper.Copy(Bool, "bridge", "delivery_receipts")
|
||||||
|
helper.Copy(Int, "bridge", "portal_message_buffer")
|
||||||
|
helper.Copy(Bool, "bridge", "call_start_notices")
|
||||||
|
helper.Copy(Bool, "bridge", "history_sync", "create_portals")
|
||||||
|
helper.Copy(Int, "bridge", "history_sync", "max_age")
|
||||||
|
helper.Copy(Bool, "bridge", "history_sync", "backfill")
|
||||||
|
helper.Copy(Bool, "bridge", "history_sync", "double_puppet_backfill")
|
||||||
|
helper.Copy(Bool, "bridge", "history_sync", "request_full_sync")
|
||||||
|
helper.Copy(Bool, "bridge", "user_avatar_sync")
|
||||||
|
helper.Copy(Bool, "bridge", "bridge_matrix_leave")
|
||||||
|
helper.Copy(Bool, "bridge", "sync_with_custom_puppets")
|
||||||
|
helper.Copy(Bool, "bridge", "sync_direct_chat_list")
|
||||||
|
helper.Copy(Bool, "bridge", "default_bridge_receipts")
|
||||||
|
helper.Copy(Bool, "bridge", "default_bridge_presence")
|
||||||
|
helper.Copy(Map, "bridge", "double_puppet_server_map")
|
||||||
|
helper.Copy(Bool, "bridge", "double_puppet_allow_discovery")
|
||||||
|
if legacySecret, ok := helper.Get(Str, "bridge", "login_shared_secret"); ok && len(legacySecret) > 0 {
|
||||||
|
baseNode := helper.GetBaseNode("bridge", "login_shared_secret_map")
|
||||||
|
baseNode.Map[helper.GetBase("homeserver", "domain")] = YAMLNode{Node: makeStringNode(legacySecret)}
|
||||||
|
baseNode.Content = baseNode.Map.toNodes()
|
||||||
|
} else {
|
||||||
|
helper.Copy(Map, "bridge", "login_shared_secret_map")
|
||||||
|
}
|
||||||
|
helper.Copy(Bool, "bridge", "private_chat_portal_meta")
|
||||||
|
helper.Copy(Bool, "bridge", "bridge_notices")
|
||||||
|
helper.Copy(Bool, "bridge", "resend_bridge_info")
|
||||||
|
helper.Copy(Bool, "bridge", "mute_bridging")
|
||||||
|
helper.Copy(Str|Null, "bridge", "archive_tag")
|
||||||
|
helper.Copy(Str|Null, "bridge", "pinned_tag")
|
||||||
|
helper.Copy(Bool, "bridge", "tag_only_on_create")
|
||||||
|
helper.Copy(Bool, "bridge", "enable_status_broadcast")
|
||||||
|
helper.Copy(Bool, "bridge", "whatsapp_thumbnail")
|
||||||
|
helper.Copy(Bool, "bridge", "allow_user_invite")
|
||||||
|
helper.Copy(Str, "bridge", "command_prefix")
|
||||||
|
helper.Copy(Bool, "bridge", "federate_rooms")
|
||||||
|
helper.Copy(Str, "bridge", "management_room_text", "welcome")
|
||||||
|
helper.Copy(Str, "bridge", "management_room_text", "welcome_connected")
|
||||||
|
helper.Copy(Str, "bridge", "management_room_text", "welcome_unconnected")
|
||||||
|
helper.Copy(Str|Null, "bridge", "management_room_text", "additional_help")
|
||||||
|
helper.Copy(Bool, "bridge", "encryption", "allow")
|
||||||
|
helper.Copy(Bool, "bridge", "encryption", "default")
|
||||||
|
helper.Copy(Bool, "bridge", "encryption", "key_sharing", "allow")
|
||||||
|
helper.Copy(Bool, "bridge", "encryption", "key_sharing", "require_cross_signing")
|
||||||
|
helper.Copy(Bool, "bridge", "encryption", "key_sharing", "require_verification")
|
||||||
|
helper.Copy(Map, "bridge", "permissions")
|
||||||
|
helper.Copy(Bool, "bridge", "relay", "enabled")
|
||||||
|
helper.Copy(Bool, "bridge", "relay", "admin_only")
|
||||||
|
helper.Copy(Map, "bridge", "relay", "message_formats")
|
||||||
|
|
||||||
|
helper.Copy(Str, "logging", "directory")
|
||||||
|
helper.Copy(Str|Null, "logging", "file_name_format")
|
||||||
|
helper.Copy(Str|Timestamp, "logging", "file_date_format")
|
||||||
|
helper.Copy(Int, "logging", "file_mode")
|
||||||
|
helper.Copy(Str|Timestamp, "logging", "timestamp_format")
|
||||||
|
helper.Copy(Str, "logging", "print_level")
|
||||||
|
}
|
||||||
|
|
||||||
|
func Mutate(path string, mutate func(helper *UpgradeHelper)) error {
|
||||||
|
_, _, err := upgrade(path, true, mutate)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func Upgrade(path string, save bool) ([]byte, bool, error) {
|
||||||
|
return upgrade(path, save, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func upgrade(path string, save bool, mutate func(helper *UpgradeHelper)) ([]byte, bool, error) {
|
||||||
|
sourceData, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, fmt.Errorf("failed to read config: %w", err)
|
||||||
|
}
|
||||||
|
var base, cfg yaml.Node
|
||||||
|
err = yaml.Unmarshal([]byte(ExampleConfig), &base)
|
||||||
|
if err != nil {
|
||||||
|
return sourceData, false, fmt.Errorf("failed to unmarshal example config: %w", err)
|
||||||
|
}
|
||||||
|
err = yaml.Unmarshal(sourceData, &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return sourceData, false, fmt.Errorf("failed to unmarshal config: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
helper := NewUpgradeHelper(&base, &cfg)
|
||||||
|
helper.doUpgrade()
|
||||||
|
if mutate != nil {
|
||||||
|
mutate(helper)
|
||||||
|
}
|
||||||
|
|
||||||
|
output, err := yaml.Marshal(&base)
|
||||||
|
if err != nil {
|
||||||
|
return sourceData, false, fmt.Errorf("failed to marshal updated config: %w", err)
|
||||||
|
}
|
||||||
|
if save {
|
||||||
|
var tempFile *os.File
|
||||||
|
tempFile, err = os.CreateTemp("", "wa-config-*.yaml")
|
||||||
|
if err != nil {
|
||||||
|
return output, true, fmt.Errorf("failed to create temp file for writing config: %w", err)
|
||||||
|
}
|
||||||
|
_, err = tempFile.Write(output)
|
||||||
|
if err != nil {
|
||||||
|
_ = os.Remove(tempFile.Name())
|
||||||
|
return output, true, fmt.Errorf("failed to write updated config to temp file: %w", err)
|
||||||
|
}
|
||||||
|
err = os.Rename(tempFile.Name(), path)
|
||||||
|
if err != nil {
|
||||||
|
_ = os.Remove(tempFile.Name())
|
||||||
|
return output, true, fmt.Errorf("failed to override current config with temp file: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return output, true, nil
|
||||||
|
}
|
276
config/upgradehelper.go
Normal file
276
config/upgradehelper.go
Normal file
|
@ -0,0 +1,276 @@
|
||||||
|
// mautrix-whatsapp - A Matrix-WhatsApp puppeting bridge.
|
||||||
|
// Copyright (C) 2021 Tulir Asokan
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
type YAMLMap map[string]YAMLNode
|
||||||
|
type YAMLList []YAMLNode
|
||||||
|
|
||||||
|
type YAMLNode struct {
|
||||||
|
*yaml.Node
|
||||||
|
Map YAMLMap
|
||||||
|
List YAMLList
|
||||||
|
}
|
||||||
|
|
||||||
|
type YAMLType uint32
|
||||||
|
|
||||||
|
const (
|
||||||
|
Null YAMLType = 1 << iota
|
||||||
|
Bool
|
||||||
|
Str
|
||||||
|
Int
|
||||||
|
Float
|
||||||
|
Timestamp
|
||||||
|
List
|
||||||
|
Map
|
||||||
|
Binary
|
||||||
|
)
|
||||||
|
|
||||||
|
func (t YAMLType) String() string {
|
||||||
|
switch t {
|
||||||
|
case Null:
|
||||||
|
return NullTag
|
||||||
|
case Bool:
|
||||||
|
return BoolTag
|
||||||
|
case Str:
|
||||||
|
return StrTag
|
||||||
|
case Int:
|
||||||
|
return IntTag
|
||||||
|
case Float:
|
||||||
|
return FloatTag
|
||||||
|
case Timestamp:
|
||||||
|
return TimestampTag
|
||||||
|
case List:
|
||||||
|
return SeqTag
|
||||||
|
case Map:
|
||||||
|
return MapTag
|
||||||
|
case Binary:
|
||||||
|
return BinaryTag
|
||||||
|
default:
|
||||||
|
panic(fmt.Errorf("can't convert type %d to string", t))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func tagToType(tag string) YAMLType {
|
||||||
|
switch tag {
|
||||||
|
case NullTag:
|
||||||
|
return Null
|
||||||
|
case BoolTag:
|
||||||
|
return Bool
|
||||||
|
case StrTag:
|
||||||
|
return Str
|
||||||
|
case IntTag:
|
||||||
|
return Int
|
||||||
|
case FloatTag:
|
||||||
|
return Float
|
||||||
|
case TimestampTag:
|
||||||
|
return Timestamp
|
||||||
|
case SeqTag:
|
||||||
|
return List
|
||||||
|
case MapTag:
|
||||||
|
return Map
|
||||||
|
case BinaryTag:
|
||||||
|
return Binary
|
||||||
|
default:
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
NullTag = "!!null"
|
||||||
|
BoolTag = "!!bool"
|
||||||
|
StrTag = "!!str"
|
||||||
|
IntTag = "!!int"
|
||||||
|
FloatTag = "!!float"
|
||||||
|
TimestampTag = "!!timestamp"
|
||||||
|
SeqTag = "!!seq"
|
||||||
|
MapTag = "!!map"
|
||||||
|
BinaryTag = "!!binary"
|
||||||
|
)
|
||||||
|
|
||||||
|
func fromNode(node *yaml.Node) YAMLNode {
|
||||||
|
switch node.Kind {
|
||||||
|
case yaml.DocumentNode:
|
||||||
|
return fromNode(node.Content[0])
|
||||||
|
case yaml.AliasNode:
|
||||||
|
return fromNode(node.Alias)
|
||||||
|
case yaml.MappingNode:
|
||||||
|
return YAMLNode{
|
||||||
|
Node: node,
|
||||||
|
Map: parseYAMLMap(node),
|
||||||
|
}
|
||||||
|
case yaml.SequenceNode:
|
||||||
|
return YAMLNode{
|
||||||
|
Node: node,
|
||||||
|
List: parseYAMLList(node),
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return YAMLNode{Node: node}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (yn *YAMLNode) toNode() *yaml.Node {
|
||||||
|
switch {
|
||||||
|
case yn.Map != nil && yn.Node.Kind == yaml.MappingNode:
|
||||||
|
yn.Content = yn.Map.toNodes()
|
||||||
|
case yn.List != nil && yn.Node.Kind == yaml.SequenceNode:
|
||||||
|
yn.Content = yn.List.toNodes()
|
||||||
|
}
|
||||||
|
return yn.Node
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseYAMLList(node *yaml.Node) YAMLList {
|
||||||
|
data := make(YAMLList, len(node.Content))
|
||||||
|
for i, item := range node.Content {
|
||||||
|
data[i] = fromNode(item)
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
func (yl YAMLList) toNodes() []*yaml.Node {
|
||||||
|
nodes := make([]*yaml.Node, len(yl))
|
||||||
|
for i, item := range yl {
|
||||||
|
nodes[i] = item.toNode()
|
||||||
|
}
|
||||||
|
return nodes
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseYAMLMap(node *yaml.Node) YAMLMap {
|
||||||
|
if len(node.Content)%2 != 0 {
|
||||||
|
panic(fmt.Errorf("uneven number of items in YAML map (%d)", len(node.Content)))
|
||||||
|
}
|
||||||
|
data := make(YAMLMap, len(node.Content)/2)
|
||||||
|
for i := 0; i < len(node.Content); i += 2 {
|
||||||
|
key := node.Content[i]
|
||||||
|
value := node.Content[i+1]
|
||||||
|
if key.Kind == yaml.ScalarNode {
|
||||||
|
data[key.Value] = fromNode(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ym YAMLMap) toNodes() []*yaml.Node {
|
||||||
|
nodes := make([]*yaml.Node, len(ym)*2)
|
||||||
|
i := 0
|
||||||
|
for key, value := range ym {
|
||||||
|
nodes[i] = makeStringNode(key)
|
||||||
|
nodes[i+1] = value.toNode()
|
||||||
|
i += 2
|
||||||
|
}
|
||||||
|
return nodes
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeStringNode(val string) *yaml.Node {
|
||||||
|
var node yaml.Node
|
||||||
|
node.SetString(val)
|
||||||
|
return &node
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpgradeHelper struct {
|
||||||
|
base YAMLNode
|
||||||
|
cfg YAMLNode
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUpgradeHelper(base, cfg *yaml.Node) *UpgradeHelper {
|
||||||
|
return &UpgradeHelper{
|
||||||
|
base: fromNode(base),
|
||||||
|
cfg: fromNode(cfg),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (helper *UpgradeHelper) Copy(allowedTypes YAMLType, path ...string) {
|
||||||
|
base, cfg := helper.base, helper.cfg
|
||||||
|
var ok bool
|
||||||
|
for _, item := range path {
|
||||||
|
base = base.Map[item]
|
||||||
|
cfg, ok = cfg.Map[item]
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if allowedTypes&tagToType(cfg.Tag) == 0 {
|
||||||
|
_, _ = fmt.Fprintf(os.Stderr, "Ignoring incorrect config field type %s at %s\n", cfg.Tag, strings.Join(path, "->"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
base.Tag = cfg.Tag
|
||||||
|
base.Style = cfg.Style
|
||||||
|
switch base.Kind {
|
||||||
|
case yaml.ScalarNode:
|
||||||
|
base.Value = cfg.Value
|
||||||
|
case yaml.SequenceNode, yaml.MappingNode:
|
||||||
|
base.Content = cfg.Content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getNode(cfg YAMLNode, path []string) *YAMLNode {
|
||||||
|
var ok bool
|
||||||
|
for _, item := range path {
|
||||||
|
cfg, ok = cfg.Map[item]
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &cfg
|
||||||
|
}
|
||||||
|
|
||||||
|
func (helper *UpgradeHelper) GetNode(path ...string) *YAMLNode {
|
||||||
|
return getNode(helper.cfg, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (helper *UpgradeHelper) GetBaseNode(path ...string) *YAMLNode {
|
||||||
|
return getNode(helper.base, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (helper *UpgradeHelper) Get(tag YAMLType, path ...string) (string, bool) {
|
||||||
|
node := helper.GetNode(path...)
|
||||||
|
if node == nil || node.Kind != yaml.ScalarNode || tag&tagToType(node.Tag) == 0 {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
return node.Value, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (helper *UpgradeHelper) GetBase(path ...string) string {
|
||||||
|
return helper.GetBaseNode(path...).Value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (helper *UpgradeHelper) Set(tag YAMLType, value string, path ...string) {
|
||||||
|
base := helper.base
|
||||||
|
for _, item := range path {
|
||||||
|
base = base.Map[item]
|
||||||
|
}
|
||||||
|
base.Tag = tag.String()
|
||||||
|
base.Value = value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (helper *UpgradeHelper) SetMap(value YAMLMap, path ...string) {
|
||||||
|
base := helper.base
|
||||||
|
for _, item := range path {
|
||||||
|
base = base.Map[item]
|
||||||
|
}
|
||||||
|
if base.Tag != MapTag || base.Kind != yaml.MappingNode {
|
||||||
|
panic(fmt.Errorf("invalid target for SetMap(%+v): tag:%s, kind:%d", path, base.Tag, base.Kind))
|
||||||
|
}
|
||||||
|
base.Content = value.toNodes()
|
||||||
|
}
|
|
@ -5,6 +5,8 @@ homeserver:
|
||||||
# The domain of the homeserver (for MXIDs, etc).
|
# The domain of the homeserver (for MXIDs, etc).
|
||||||
domain: example.com
|
domain: example.com
|
||||||
|
|
||||||
|
# Is the homeserver actually mautrix-asmux?
|
||||||
|
asmux: false
|
||||||
# The URL to push real-time bridge status to.
|
# The URL to push real-time bridge status to.
|
||||||
# If set, the bridge will make POST requests to this URL whenever a user's whatsapp connection state changes.
|
# If set, the bridge will make POST requests to this URL whenever a user's whatsapp connection state changes.
|
||||||
# The bridge will use the appservice as_token to authorize requests.
|
# The bridge will use the appservice as_token to authorize requests.
|
||||||
|
@ -37,8 +39,9 @@ appservice:
|
||||||
provisioning:
|
provisioning:
|
||||||
# Prefix for the provisioning API paths.
|
# Prefix for the provisioning API paths.
|
||||||
prefix: /_matrix/provision/v1
|
prefix: /_matrix/provision/v1
|
||||||
# Shared secret for authentication. If set to "disable", the provisioning API will be disabled.
|
# Shared secret for authentication. If set to "generate", a random secret will be generated,
|
||||||
shared_secret: disable
|
# or if set to "disable", the provisioning API will be disabled.
|
||||||
|
shared_secret: generate
|
||||||
|
|
||||||
# The unique ID of this appservice.
|
# The unique ID of this appservice.
|
||||||
id: whatsapp
|
id: whatsapp
|
||||||
|
@ -55,12 +58,14 @@ appservice:
|
||||||
as_token: "This value is generated when generating the registration"
|
as_token: "This value is generated when generating the registration"
|
||||||
hs_token: "This value is generated when generating the registration"
|
hs_token: "This value is generated when generating the registration"
|
||||||
|
|
||||||
|
# Prometheus config.
|
||||||
metrics:
|
metrics:
|
||||||
# Enable prometheus metrics?
|
# Enable prometheus metrics?
|
||||||
enabled: false
|
enabled: false
|
||||||
# IP and port where the metrics listener should be. The path is always /metrics
|
# IP and port where the metrics listener should be. The path is always /metrics
|
||||||
listen: 127.0.0.1:8001
|
listen: 127.0.0.1:8001
|
||||||
|
|
||||||
|
# Config for things that are directly sent to WhatsApp.
|
||||||
whatsapp:
|
whatsapp:
|
||||||
# Device name that's shown in the "WhatsApp Web" section in the mobile app.
|
# Device name that's shown in the "WhatsApp Web" section in the mobile app.
|
||||||
os_name: Mautrix-WhatsApp bridge
|
os_name: Mautrix-WhatsApp bridge
|
||||||
|
@ -87,6 +92,7 @@ bridge:
|
||||||
delivery_receipts: false
|
delivery_receipts: false
|
||||||
# Should incoming calls send a message to the Matrix room?
|
# Should incoming calls send a message to the Matrix room?
|
||||||
call_start_notices: true
|
call_start_notices: true
|
||||||
|
portal_message_buffer: 128
|
||||||
|
|
||||||
# Settings for handling history sync payloads. These settings only apply right after login,
|
# Settings for handling history sync payloads. These settings only apply right after login,
|
||||||
# because the phone only sends the history sync data once, and there's no way to re-request it
|
# because the phone only sends the history sync data once, and there's no way to re-request it
|
||||||
|
@ -158,22 +164,19 @@ bridge:
|
||||||
# Should WhatsApp status messages be bridged into a Matrix room?
|
# Should WhatsApp status messages be bridged into a Matrix room?
|
||||||
# Disabling this won't affect already created status broadcast rooms.
|
# Disabling this won't affect already created status broadcast rooms.
|
||||||
enable_status_broadcast: true
|
enable_status_broadcast: true
|
||||||
|
|
||||||
# Should the bridge use thumbnails from WhatsApp?
|
# Should the bridge use thumbnails from WhatsApp?
|
||||||
# They're disabled by default due to very low resolution.
|
# They're disabled by default due to very low resolution.
|
||||||
whatsapp_thumbnail: false
|
whatsapp_thumbnail: false
|
||||||
|
|
||||||
# Allow invite permission for user. User can invite any bots to room with whatsapp
|
# Allow invite permission for user. User can invite any bots to room with whatsapp
|
||||||
# users (private chat and groups)
|
# users (private chat and groups)
|
||||||
allow_user_invite: false
|
allow_user_invite: false
|
||||||
|
|
||||||
# The prefix for commands. Only required in non-management rooms.
|
|
||||||
command_prefix: "!wa"
|
|
||||||
|
|
||||||
# Whether or not created rooms should have federation enabled.
|
# Whether or not created rooms should have federation enabled.
|
||||||
# If false, created portal rooms will never be federated.
|
# If false, created portal rooms will never be federated.
|
||||||
federate_rooms: true
|
federate_rooms: true
|
||||||
|
|
||||||
|
# The prefix for commands. Only required in non-management rooms.
|
||||||
|
command_prefix: "!wa"
|
||||||
|
|
||||||
# Messages sent upon joining a management room.
|
# Messages sent upon joining a management room.
|
||||||
# Markdown is supported. The defaults are listed below.
|
# Markdown is supported. The defaults are listed below.
|
||||||
management_room_text:
|
management_room_text:
|
||||||
|
@ -184,7 +187,7 @@ bridge:
|
||||||
# Sent when joining a management room and the user is not logged in.
|
# Sent when joining a management room and the user is not logged in.
|
||||||
welcome_unconnected: "Use `help` for help or `login` to log in."
|
welcome_unconnected: "Use `help` for help or `login` to log in."
|
||||||
# Optional extra text sent when joining a management room.
|
# Optional extra text sent when joining a management room.
|
||||||
# additional_help: "This would be some additional text in case you need it."
|
additional_help: ""
|
||||||
|
|
||||||
# End-to-bridge encryption support options.
|
# End-to-bridge encryption support options.
|
||||||
#
|
#
|
||||||
|
@ -223,6 +226,7 @@ bridge:
|
||||||
"example.com": user
|
"example.com": user
|
||||||
"@admin:example.com": admin
|
"@admin:example.com": admin
|
||||||
|
|
||||||
|
# Settings for relay mode
|
||||||
relay:
|
relay:
|
||||||
# Whether relay mode should be allowed. If allowed, `!wa set-relay` can be used to turn any
|
# Whether relay mode should be allowed. If allowed, `!wa set-relay` can be used to turn any
|
||||||
# authenticated user into a relaybot for that chat.
|
# authenticated user into a relaybot for that chat.
|
||||||
|
@ -248,11 +252,11 @@ logging:
|
||||||
# Set this to null to disable logging to file.
|
# Set this to null to disable logging to file.
|
||||||
file_name_format: "{{.Date}}-{{.Index}}.log"
|
file_name_format: "{{.Date}}-{{.Index}}.log"
|
||||||
# Date format for file names in the Go time format: https://golang.org/pkg/time/#pkg-constants
|
# Date format for file names in the Go time format: https://golang.org/pkg/time/#pkg-constants
|
||||||
file_date_format: 2006-01-02
|
file_date_format: "2006-01-02"
|
||||||
# Log file permissions.
|
# Log file permissions.
|
||||||
file_mode: 0600
|
file_mode: 0o600
|
||||||
# Timestamp format for log entries in the Go time format.
|
# Timestamp format for log entries in the Go time format.
|
||||||
timestamp_format: Jan _2, 2006 15:04:05
|
timestamp_format: "Jan _2, 2006 15:04:05"
|
||||||
# Minimum severity for log messages printed to stdout/stderr. This doesn't affect the log file.
|
# Minimum severity for log messages printed to stdout/stderr. This doesn't affect the log file.
|
||||||
# Options: debug, info, warn, error, fatal
|
# Options: debug, info, warn, error, fatal
|
||||||
print_level: debug
|
print_level: debug
|
||||||
|
|
3
go.mod
3
go.mod
|
@ -11,7 +11,7 @@ require (
|
||||||
go.mau.fi/whatsmeow v0.0.0-20211105153256-c8ed00a3615d
|
go.mau.fi/whatsmeow v0.0.0-20211105153256-c8ed00a3615d
|
||||||
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d
|
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d
|
||||||
google.golang.org/protobuf v1.27.1
|
google.golang.org/protobuf v1.27.1
|
||||||
gopkg.in/yaml.v2 v2.4.0
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
|
||||||
maunium.net/go/mauflag v1.0.0
|
maunium.net/go/mauflag v1.0.0
|
||||||
maunium.net/go/maulogger/v2 v2.3.1
|
maunium.net/go/maulogger/v2 v2.3.1
|
||||||
maunium.net/go/mautrix v0.10.1
|
maunium.net/go/mautrix v0.10.1
|
||||||
|
@ -37,4 +37,5 @@ require (
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect
|
||||||
golang.org/x/net v0.0.0-20211020060615-d418f374d309 // indirect
|
golang.org/x/net v0.0.0-20211020060615-d418f374d309 // indirect
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 // indirect
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 // indirect
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
3
go.sum
3
go.sum
|
@ -213,8 +213,9 @@ gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
maunium.net/go/mauflag v1.0.0 h1:YiaRc0tEI3toYtJMRIfjP+jklH45uDHtT80nUamyD4M=
|
maunium.net/go/mauflag v1.0.0 h1:YiaRc0tEI3toYtJMRIfjP+jklH45uDHtT80nUamyD4M=
|
||||||
maunium.net/go/mauflag v1.0.0/go.mod h1:nLivPOpTpHnpzEh8jEdSL9UqO9+/KBJFmNRlwKfkPeA=
|
maunium.net/go/mauflag v1.0.0/go.mod h1:nLivPOpTpHnpzEh8jEdSL9UqO9+/KBJFmNRlwKfkPeA=
|
||||||
maunium.net/go/maulogger/v2 v2.3.1 h1:fwBYJne0pHvJrrIPHK+TAPfyxxbBEz46oVGez2x0ODE=
|
maunium.net/go/maulogger/v2 v2.3.1 h1:fwBYJne0pHvJrrIPHK+TAPfyxxbBEz46oVGez2x0ODE=
|
||||||
|
|
70
main.go
70
main.go
|
@ -97,8 +97,7 @@ func init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
var configPath = flag.MakeFull("c", "config", "The path to your config file.", "config.yaml").String()
|
var configPath = flag.MakeFull("c", "config", "The path to your config file.", "config.yaml").String()
|
||||||
|
var dontSaveConfig = flag.MakeFull("n", "no-update", "Don't save updated config to disk.", "false").Bool()
|
||||||
//var baseConfigPath = flag.MakeFull("b", "base-config", "The path to the example config file.", "example-config.yaml").String()
|
|
||||||
var registrationPath = flag.MakeFull("r", "registration", "The path where to save the appservice registration.", "registration.yaml").String()
|
var registrationPath = flag.MakeFull("r", "registration", "The path where to save the appservice registration.", "registration.yaml").String()
|
||||||
var generateRegistration = flag.MakeFull("g", "generate-registration", "Generate registration and quit.", "false").Bool()
|
var generateRegistration = flag.MakeFull("g", "generate-registration", "Generate registration and quit.", "false").Bool()
|
||||||
var version = flag.MakeFull("v", "version", "View bridge version and quit.", "false").Bool()
|
var version = flag.MakeFull("v", "version", "View bridge version and quit.", "false").Bool()
|
||||||
|
@ -107,6 +106,11 @@ var migrateFrom = flag.Make().LongKey("migrate-db").Usage("Source database type
|
||||||
var wantHelp, _ = flag.MakeHelpFlag()
|
var wantHelp, _ = flag.MakeHelpFlag()
|
||||||
|
|
||||||
func (bridge *Bridge) GenerateRegistration() {
|
func (bridge *Bridge) GenerateRegistration() {
|
||||||
|
if *dontSaveConfig {
|
||||||
|
// We need to save the generated as_token and hs_token in the config
|
||||||
|
_, _ = fmt.Fprintln(os.Stderr, "--no-update is not compatible with --generate-registration")
|
||||||
|
os.Exit(5)
|
||||||
|
}
|
||||||
reg, err := bridge.Config.NewRegistration()
|
reg, err := bridge.Config.NewRegistration()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_, _ = fmt.Fprintln(os.Stderr, "Failed to generate registration:", err)
|
_, _ = fmt.Fprintln(os.Stderr, "Failed to generate registration:", err)
|
||||||
|
@ -119,7 +123,10 @@ func (bridge *Bridge) GenerateRegistration() {
|
||||||
os.Exit(21)
|
os.Exit(21)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = bridge.Config.Save(*configPath)
|
err = config.Mutate(*configPath, func(helper *config.UpgradeHelper) {
|
||||||
|
helper.Set(config.Str, bridge.Config.AppService.ASToken, "appservice", "as_token")
|
||||||
|
helper.Set(config.Str, bridge.Config.AppService.HSToken, "appservice", "hs_token")
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_, _ = fmt.Fprintln(os.Stderr, "Failed to save config:", err)
|
_, _ = fmt.Fprintln(os.Stderr, "Failed to save config:", err)
|
||||||
os.Exit(22)
|
os.Exit(22)
|
||||||
|
@ -193,26 +200,6 @@ type Crypto interface {
|
||||||
Stop()
|
Stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewBridge() *Bridge {
|
|
||||||
bridge := &Bridge{
|
|
||||||
usersByMXID: make(map[id.UserID]*User),
|
|
||||||
usersByUsername: make(map[string]*User),
|
|
||||||
managementRooms: make(map[id.RoomID]*User),
|
|
||||||
portalsByMXID: make(map[id.RoomID]*Portal),
|
|
||||||
portalsByJID: make(map[database.PortalKey]*Portal),
|
|
||||||
puppets: make(map[types.JID]*Puppet),
|
|
||||||
puppetsByCustomMXID: make(map[id.UserID]*Puppet),
|
|
||||||
}
|
|
||||||
|
|
||||||
var err error
|
|
||||||
bridge.Config, err = config.Load(*configPath)
|
|
||||||
if err != nil {
|
|
||||||
_, _ = fmt.Fprintln(os.Stderr, "Failed to load config:", err)
|
|
||||||
os.Exit(10)
|
|
||||||
}
|
|
||||||
return bridge
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bridge *Bridge) ensureConnection() {
|
func (bridge *Bridge) ensureConnection() {
|
||||||
for {
|
for {
|
||||||
resp, err := bridge.Bot.Whoami()
|
resp, err := bridge.Bot.Whoami()
|
||||||
|
@ -344,10 +331,15 @@ func (bridge *Bridge) Start() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bridge *Bridge) ResendBridgeInfo() {
|
func (bridge *Bridge) ResendBridgeInfo() {
|
||||||
bridge.Config.Bridge.ResendBridgeInfo = false
|
if *dontSaveConfig {
|
||||||
err := bridge.Config.Save(*configPath)
|
bridge.Log.Warnln("Not setting resend_bridge_info to false in config due to --no-update flag")
|
||||||
if err != nil {
|
} else {
|
||||||
bridge.Log.Errorln("Failed to save config after setting resend_bridge_info to false:", err)
|
err := config.Mutate(*configPath, func(helper *config.UpgradeHelper) {
|
||||||
|
helper.Set(config.Bool, "false", "bridge", "resend_bridge_info")
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
bridge.Log.Errorln("Failed to save config after setting resend_bridge_info to false:", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
bridge.Log.Infoln("Re-sending bridge info state event to all portals")
|
bridge.Log.Infoln("Re-sending bridge info state event to all portals")
|
||||||
for _, portal := range bridge.GetAllPortals() {
|
for _, portal := range bridge.GetAllPortals() {
|
||||||
|
@ -426,6 +418,20 @@ func (bridge *Bridge) Stop() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bridge *Bridge) Main() {
|
func (bridge *Bridge) Main() {
|
||||||
|
configData, upgraded, err := config.Upgrade(*configPath, !*dontSaveConfig)
|
||||||
|
if err != nil {
|
||||||
|
_, _ = fmt.Fprintln(os.Stderr, "Error updating config:", err)
|
||||||
|
if configData == nil {
|
||||||
|
os.Exit(10)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bridge.Config, err = config.Load(configData, upgraded)
|
||||||
|
if err != nil {
|
||||||
|
_, _ = fmt.Fprintln(os.Stderr, "Failed to parse config:", err)
|
||||||
|
os.Exit(10)
|
||||||
|
}
|
||||||
|
|
||||||
if *generateRegistration {
|
if *generateRegistration {
|
||||||
bridge.GenerateRegistration()
|
bridge.GenerateRegistration()
|
||||||
return
|
return
|
||||||
|
@ -466,5 +472,13 @@ func main() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
NewBridge().Main()
|
(&Bridge{
|
||||||
|
usersByMXID: make(map[id.UserID]*User),
|
||||||
|
usersByUsername: make(map[string]*User),
|
||||||
|
managementRooms: make(map[id.RoomID]*User),
|
||||||
|
portalsByMXID: make(map[id.RoomID]*Portal),
|
||||||
|
portalsByJID: make(map[database.PortalKey]*Portal),
|
||||||
|
puppets: make(map[types.JID]*Puppet),
|
||||||
|
puppetsByCustomMXID: make(map[id.UserID]*Puppet),
|
||||||
|
}).Main()
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue