|
@@ -18,43 +18,26 @@ package main
|
|
|
|
|
|
import (
|
|
import (
|
|
_ "embed"
|
|
_ "embed"
|
|
- "errors"
|
|
|
|
- "fmt"
|
|
|
|
"net/http"
|
|
"net/http"
|
|
"os"
|
|
"os"
|
|
- "os/signal"
|
|
|
|
"strconv"
|
|
"strconv"
|
|
"strings"
|
|
"strings"
|
|
"sync"
|
|
"sync"
|
|
- "syscall"
|
|
|
|
"time"
|
|
"time"
|
|
|
|
|
|
- "google.golang.org/protobuf/proto"
|
|
|
|
-
|
|
|
|
"go.mau.fi/whatsmeow"
|
|
"go.mau.fi/whatsmeow"
|
|
waProto "go.mau.fi/whatsmeow/binary/proto"
|
|
waProto "go.mau.fi/whatsmeow/binary/proto"
|
|
"go.mau.fi/whatsmeow/store"
|
|
"go.mau.fi/whatsmeow/store"
|
|
"go.mau.fi/whatsmeow/store/sqlstore"
|
|
"go.mau.fi/whatsmeow/store/sqlstore"
|
|
"go.mau.fi/whatsmeow/types"
|
|
"go.mau.fi/whatsmeow/types"
|
|
|
|
+ "google.golang.org/protobuf/proto"
|
|
|
|
|
|
- flag "maunium.net/go/mauflag"
|
|
|
|
- log "maunium.net/go/maulogger/v2"
|
|
|
|
-
|
|
|
|
- "maunium.net/go/mautrix"
|
|
|
|
- "maunium.net/go/mautrix/appservice"
|
|
|
|
- "maunium.net/go/mautrix/event"
|
|
|
|
|
|
+ "maunium.net/go/mautrix/bridge"
|
|
"maunium.net/go/mautrix/id"
|
|
"maunium.net/go/mautrix/id"
|
|
"maunium.net/go/mautrix/util/configupgrade"
|
|
"maunium.net/go/mautrix/util/configupgrade"
|
|
|
|
|
|
"maunium.net/go/mautrix-whatsapp/config"
|
|
"maunium.net/go/mautrix-whatsapp/config"
|
|
"maunium.net/go/mautrix-whatsapp/database"
|
|
"maunium.net/go/mautrix-whatsapp/database"
|
|
- "maunium.net/go/mautrix-whatsapp/database/upgrades"
|
|
|
|
-)
|
|
|
|
-
|
|
|
|
-// The name and repo URL of the bridge.
|
|
|
|
-var (
|
|
|
|
- Name = "mautrix-whatsapp"
|
|
|
|
- URL = "https://github.com/mautrix/whatsapp"
|
|
|
|
)
|
|
)
|
|
|
|
|
|
// Information to find out exactly which commit the bridge was built from.
|
|
// Information to find out exactly which commit the bridge was built from.
|
|
@@ -65,120 +48,19 @@ var (
|
|
BuildTime = "unknown"
|
|
BuildTime = "unknown"
|
|
)
|
|
)
|
|
|
|
|
|
-var (
|
|
|
|
- // Version is the version number of the bridge. Changed manually when making a release.
|
|
|
|
- Version = "0.4.0"
|
|
|
|
- // WAVersion is the version number exposed to WhatsApp. Filled in init()
|
|
|
|
- WAVersion = ""
|
|
|
|
- // VersionString is the bridge version, plus commit information. Filled in init() using the build-time values.
|
|
|
|
- VersionString = ""
|
|
|
|
-)
|
|
|
|
-
|
|
|
|
//go:embed example-config.yaml
|
|
//go:embed example-config.yaml
|
|
var ExampleConfig string
|
|
var ExampleConfig string
|
|
|
|
|
|
-func init() {
|
|
|
|
- if len(Tag) > 0 && Tag[0] == 'v' {
|
|
|
|
- Tag = Tag[1:]
|
|
|
|
- }
|
|
|
|
- if Tag != Version {
|
|
|
|
- suffix := ""
|
|
|
|
- if !strings.HasSuffix(Version, "+dev") {
|
|
|
|
- suffix = "+dev"
|
|
|
|
- }
|
|
|
|
- if len(Commit) > 8 {
|
|
|
|
- Version = fmt.Sprintf("%s%s.%s", Version, suffix, Commit[:8])
|
|
|
|
- } else {
|
|
|
|
- Version = fmt.Sprintf("%s%s.unknown", Version, suffix)
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- mautrix.DefaultUserAgent = fmt.Sprintf("mautrix-whatsapp/%s %s", Version, mautrix.DefaultUserAgent)
|
|
|
|
- WAVersion = strings.FieldsFunc(Version, func(r rune) bool { return r == '-' || r == '+' })[0]
|
|
|
|
- VersionString = fmt.Sprintf("%s %s (%s)", Name, Version, BuildTime)
|
|
|
|
-
|
|
|
|
- config.ExampleConfig = ExampleConfig
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-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 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 version = flag.MakeFull("v", "version", "View bridge version and quit.", "false").Bool()
|
|
|
|
-var ignoreUnsupportedDatabase = flag.Make().LongKey("ignore-unsupported-database").Usage("Run even if the database schema is too new").Default("false").Bool()
|
|
|
|
-var ignoreForeignTables = flag.Make().LongKey("ignore-foreign-tables").Usage("Run even if the database contains tables from other programs (like Synapse)").Default("false").Bool()
|
|
|
|
-var migrateFrom = flag.Make().LongKey("migrate-db").Usage("Source database type and URI to migrate from.").Bool()
|
|
|
|
-var wantHelp, _ = flag.MakeHelpFlag()
|
|
|
|
-
|
|
|
|
-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()
|
|
|
|
- if err != nil {
|
|
|
|
- _, _ = fmt.Fprintln(os.Stderr, "Failed to generate registration:", err)
|
|
|
|
- os.Exit(20)
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- err = reg.Save(*registrationPath)
|
|
|
|
- if err != nil {
|
|
|
|
- _, _ = fmt.Fprintln(os.Stderr, "Failed to save registration:", err)
|
|
|
|
- os.Exit(21)
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- err = config.Mutate(*configPath, func(helper *configupgrade.Helper) {
|
|
|
|
- helper.Set(configupgrade.Str, bridge.Config.AppService.ASToken, "appservice", "as_token")
|
|
|
|
- helper.Set(configupgrade.Str, bridge.Config.AppService.HSToken, "appservice", "hs_token")
|
|
|
|
- })
|
|
|
|
- if err != nil {
|
|
|
|
- _, _ = fmt.Fprintln(os.Stderr, "Failed to save config:", err)
|
|
|
|
- os.Exit(22)
|
|
|
|
- }
|
|
|
|
- fmt.Println("Registration generated. Add the path to the registration to your Synapse config, restart it, then start the bridge.")
|
|
|
|
- os.Exit(0)
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-func (bridge *Bridge) MigrateDatabase() {
|
|
|
|
- oldDB, err := database.New(config.DatabaseConfig{Type: flag.Arg(0), URI: flag.Arg(1)}, log.DefaultLogger)
|
|
|
|
- if err != nil {
|
|
|
|
- fmt.Println("Failed to open old database:", err)
|
|
|
|
- os.Exit(30)
|
|
|
|
- }
|
|
|
|
- err = oldDB.Init()
|
|
|
|
- if err != nil {
|
|
|
|
- fmt.Println("Failed to upgrade old database:", err)
|
|
|
|
- os.Exit(31)
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- newDB, err := database.New(bridge.Config.AppService.Database, log.DefaultLogger)
|
|
|
|
- if err != nil {
|
|
|
|
- fmt.Println("Failed to open new database:", err)
|
|
|
|
- os.Exit(32)
|
|
|
|
- }
|
|
|
|
- err = newDB.Init()
|
|
|
|
- if err != nil {
|
|
|
|
- fmt.Println("Failed to upgrade new database:", err)
|
|
|
|
- os.Exit(33)
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- database.Migrate(oldDB, newDB)
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-type Bridge struct {
|
|
|
|
- AS *appservice.AppService
|
|
|
|
- EventProcessor *appservice.EventProcessor
|
|
|
|
- MatrixHandler *MatrixHandler
|
|
|
|
- Config *config.Config
|
|
|
|
- DB *database.Database
|
|
|
|
- Log log.Logger
|
|
|
|
- StateStore *database.SQLStateStore
|
|
|
|
- Provisioning *ProvisioningAPI
|
|
|
|
- Bot *appservice.IntentAPI
|
|
|
|
- Formatter *Formatter
|
|
|
|
- Crypto Crypto
|
|
|
|
- Metrics *MetricsHandler
|
|
|
|
- WAContainer *sqlstore.Container
|
|
|
|
|
|
+type WABridge struct {
|
|
|
|
+ bridge.Bridge
|
|
|
|
+ MatrixHandler *MatrixHandler
|
|
|
|
+ Config *config.Config
|
|
|
|
+ DB *database.Database
|
|
|
|
+ Provisioning *ProvisioningAPI
|
|
|
|
+ Formatter *Formatter
|
|
|
|
+ Metrics *MetricsHandler
|
|
|
|
+ WAContainer *sqlstore.Container
|
|
|
|
+ WAVersion string
|
|
|
|
|
|
usersByMXID map[id.UserID]*User
|
|
usersByMXID map[id.UserID]*User
|
|
usersByUsername map[string]*User
|
|
usersByUsername map[string]*User
|
|
@@ -195,111 +77,32 @@ type Bridge struct {
|
|
puppetsLock sync.Mutex
|
|
puppetsLock sync.Mutex
|
|
}
|
|
}
|
|
|
|
|
|
-type Crypto interface {
|
|
|
|
- HandleMemberEvent(*event.Event)
|
|
|
|
- Decrypt(*event.Event) (*event.Event, error)
|
|
|
|
- Encrypt(id.RoomID, event.Type, event.Content) (*event.EncryptedEventContent, error)
|
|
|
|
- WaitForSession(id.RoomID, id.SenderKey, id.SessionID, time.Duration) bool
|
|
|
|
- RequestSession(id.RoomID, id.SenderKey, id.SessionID, id.UserID, id.DeviceID)
|
|
|
|
- ResetSession(id.RoomID)
|
|
|
|
- Init() error
|
|
|
|
- Start()
|
|
|
|
- Stop()
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-func (bridge *Bridge) ensureConnection() {
|
|
|
|
- for {
|
|
|
|
- versions, err := bridge.Bot.Versions()
|
|
|
|
- if err != nil {
|
|
|
|
- bridge.Log.Errorfln("Failed to connect to homeserver: %v. Retrying in 10 seconds...", err)
|
|
|
|
- time.Sleep(10 * time.Second)
|
|
|
|
- continue
|
|
|
|
- }
|
|
|
|
- if !versions.ContainsGreaterOrEqual(mautrix.SpecV11) {
|
|
|
|
- bridge.Log.Warnfln("Server isn't advertising modern spec versions")
|
|
|
|
- }
|
|
|
|
- resp, err := bridge.Bot.Whoami()
|
|
|
|
- if err != nil {
|
|
|
|
- if errors.Is(err, mautrix.MUnknownToken) {
|
|
|
|
- bridge.Log.Fatalln("The as_token was not accepted. Is the registration file installed in your homeserver correctly?")
|
|
|
|
- os.Exit(16)
|
|
|
|
- } else if errors.Is(err, mautrix.MExclusive) {
|
|
|
|
- bridge.Log.Fatalln("The as_token was accepted, but the /register request was not. Are the homeserver domain and username template in the config correct, and do they match the values in the registration?")
|
|
|
|
- os.Exit(16)
|
|
|
|
- }
|
|
|
|
- bridge.Log.Errorfln("Failed to connect to homeserver: %v. Retrying in 10 seconds...", err)
|
|
|
|
- time.Sleep(10 * time.Second)
|
|
|
|
- } else if resp.UserID != bridge.Bot.UserID {
|
|
|
|
- bridge.Log.Fatalln("Unexpected user ID in whoami call: got %s, expected %s", resp.UserID, bridge.Bot.UserID)
|
|
|
|
- os.Exit(17)
|
|
|
|
- } else {
|
|
|
|
- break
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-func (bridge *Bridge) Init() {
|
|
|
|
- var err error
|
|
|
|
-
|
|
|
|
- bridge.AS, err = bridge.Config.MakeAppService()
|
|
|
|
- if err != nil {
|
|
|
|
- _, _ = fmt.Fprintln(os.Stderr, "Failed to initialize AppService:", err)
|
|
|
|
- os.Exit(11)
|
|
|
|
- }
|
|
|
|
- _, _ = bridge.AS.Init()
|
|
|
|
-
|
|
|
|
- bridge.Log = log.Create()
|
|
|
|
- bridge.Config.Logging.Configure(bridge.Log)
|
|
|
|
- log.DefaultLogger = bridge.Log.(*log.BasicLogger)
|
|
|
|
- if len(bridge.Config.Logging.FileNameFormat) > 0 {
|
|
|
|
- err = log.OpenFile()
|
|
|
|
- if err != nil {
|
|
|
|
- _, _ = fmt.Fprintln(os.Stderr, "Failed to open log file:", err)
|
|
|
|
- os.Exit(12)
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- bridge.AS.Log = log.Sub("Matrix")
|
|
|
|
- bridge.Bot = bridge.AS.BotIntent()
|
|
|
|
- bridge.Log.Infoln("Initializing", VersionString)
|
|
|
|
-
|
|
|
|
- bridge.Log.Debugln("Initializing database connection")
|
|
|
|
- bridge.DB, err = database.New(bridge.Config.AppService.Database, bridge.Log)
|
|
|
|
- if err != nil {
|
|
|
|
- bridge.Log.Fatalln("Failed to initialize database connection:", err)
|
|
|
|
- os.Exit(14)
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- bridge.Log.Debugln("Initializing state store")
|
|
|
|
- bridge.StateStore = database.NewSQLStateStore(bridge.DB)
|
|
|
|
- bridge.AS.StateStore = bridge.StateStore
|
|
|
|
-
|
|
|
|
- Segment.log = bridge.Log.Sub("Segment")
|
|
|
|
- Segment.key = bridge.Config.SegmentKey
|
|
|
|
|
|
+func (br *WABridge) Init() {
|
|
|
|
+ Segment.log = br.Log.Sub("Segment")
|
|
|
|
+ Segment.key = br.Config.SegmentKey
|
|
if Segment.IsEnabled() {
|
|
if Segment.IsEnabled() {
|
|
Segment.log.Infoln("Segment metrics are enabled")
|
|
Segment.log.Infoln("Segment metrics are enabled")
|
|
}
|
|
}
|
|
|
|
|
|
- bridge.WAContainer = sqlstore.NewWithDB(bridge.DB.DB, bridge.Config.AppService.Database.Type, nil)
|
|
|
|
- bridge.WAContainer.DatabaseErrorHandler = bridge.DB.HandleSignalStoreError
|
|
|
|
|
|
+ br.DB = database.New(br.Bridge.DB)
|
|
|
|
+ br.WAContainer = sqlstore.NewWithDB(br.DB.DB, br.DB.Dialect.String(), nil)
|
|
|
|
+ br.WAContainer.DatabaseErrorHandler = br.DB.HandleSignalStoreError
|
|
|
|
|
|
- ss := bridge.Config.AppService.Provisioning.SharedSecret
|
|
|
|
|
|
+ ss := br.Config.Bridge.Provisioning.SharedSecret
|
|
if len(ss) > 0 && ss != "disable" {
|
|
if len(ss) > 0 && ss != "disable" {
|
|
- bridge.Provisioning = &ProvisioningAPI{bridge: bridge}
|
|
|
|
|
|
+ br.Provisioning = &ProvisioningAPI{bridge: br}
|
|
}
|
|
}
|
|
|
|
|
|
- bridge.Log.Debugln("Initializing Matrix event processor")
|
|
|
|
- bridge.EventProcessor = appservice.NewEventProcessor(bridge.AS)
|
|
|
|
- bridge.Log.Debugln("Initializing Matrix event handler")
|
|
|
|
- bridge.MatrixHandler = NewMatrixHandler(bridge)
|
|
|
|
- bridge.Formatter = NewFormatter(bridge)
|
|
|
|
- bridge.Crypto = NewCryptoHelper(bridge)
|
|
|
|
- bridge.Metrics = NewMetricsHandler(bridge.Config.Metrics.Listen, bridge.Log.Sub("Metrics"), bridge.DB)
|
|
|
|
|
|
+ br.Log.Debugln("Initializing Matrix event handler")
|
|
|
|
+ br.MatrixHandler = NewMatrixHandler(br)
|
|
|
|
+ br.Formatter = NewFormatter(br)
|
|
|
|
+ br.Metrics = NewMetricsHandler(br.Config.Metrics.Listen, br.Log.Sub("Metrics"), br.DB)
|
|
|
|
|
|
- store.BaseClientPayload.UserAgent.OsVersion = proto.String(WAVersion)
|
|
|
|
- store.BaseClientPayload.UserAgent.OsBuildNumber = proto.String(WAVersion)
|
|
|
|
- store.CompanionProps.Os = proto.String(bridge.Config.WhatsApp.OSName)
|
|
|
|
- store.CompanionProps.RequireFullSync = proto.Bool(bridge.Config.Bridge.HistorySync.RequestFullSync)
|
|
|
|
- versionParts := strings.Split(WAVersion, ".")
|
|
|
|
|
|
+ store.BaseClientPayload.UserAgent.OsVersion = proto.String(br.WAVersion)
|
|
|
|
+ store.BaseClientPayload.UserAgent.OsBuildNumber = proto.String(br.WAVersion)
|
|
|
|
+ store.CompanionProps.Os = proto.String(br.Config.WhatsApp.OSName)
|
|
|
|
+ store.CompanionProps.RequireFullSync = proto.Bool(br.Config.Bridge.HistorySync.RequestFullSync)
|
|
|
|
+ versionParts := strings.Split(br.WAVersion, ".")
|
|
if len(versionParts) > 2 {
|
|
if len(versionParts) > 2 {
|
|
primary, _ := strconv.Atoi(versionParts[0])
|
|
primary, _ := strconv.Atoi(versionParts[0])
|
|
secondary, _ := strconv.Atoi(versionParts[1])
|
|
secondary, _ := strconv.Atoi(versionParts[1])
|
|
@@ -308,161 +111,107 @@ func (bridge *Bridge) Init() {
|
|
store.CompanionProps.Version.Secondary = proto.Uint32(uint32(secondary))
|
|
store.CompanionProps.Version.Secondary = proto.Uint32(uint32(secondary))
|
|
store.CompanionProps.Version.Tertiary = proto.Uint32(uint32(tertiary))
|
|
store.CompanionProps.Version.Tertiary = proto.Uint32(uint32(tertiary))
|
|
}
|
|
}
|
|
- platformID, ok := waProto.CompanionProps_CompanionPropsPlatformType_value[strings.ToUpper(bridge.Config.WhatsApp.BrowserName)]
|
|
|
|
|
|
+ platformID, ok := waProto.CompanionProps_CompanionPropsPlatformType_value[strings.ToUpper(br.Config.WhatsApp.BrowserName)]
|
|
if ok {
|
|
if ok {
|
|
store.CompanionProps.PlatformType = waProto.CompanionProps_CompanionPropsPlatformType(platformID).Enum()
|
|
store.CompanionProps.PlatformType = waProto.CompanionProps_CompanionPropsPlatformType(platformID).Enum()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
-func (bridge *Bridge) Start() {
|
|
|
|
- bridge.Log.Debugln("Running database upgrades")
|
|
|
|
- err := bridge.DB.Init()
|
|
|
|
- if err != nil && (!errors.Is(err, upgrades.ErrUnsupportedDatabaseVersion) || !*ignoreUnsupportedDatabase) {
|
|
|
|
- bridge.Log.Fatalln("Failed to initialize database:", err)
|
|
|
|
- if errors.Is(err, upgrades.ErrForeignTables) {
|
|
|
|
- bridge.Log.Infoln("You can use --ignore-foreign-tables to ignore this error")
|
|
|
|
- } else if errors.Is(err, upgrades.ErrNotOwned) {
|
|
|
|
- bridge.Log.Infoln("Sharing the same database with different programs is not supported")
|
|
|
|
- } else if errors.Is(err, upgrades.ErrUnsupportedDatabaseVersion) {
|
|
|
|
- bridge.Log.Infoln("Downgrading the bridge is not supported")
|
|
|
|
- }
|
|
|
|
|
|
+func (br *WABridge) Start() {
|
|
|
|
+ err := br.WAContainer.Upgrade()
|
|
|
|
+ if err != nil {
|
|
|
|
+ br.Log.Fatalln("Failed to upgrade whatsmeow database: %v", err)
|
|
os.Exit(15)
|
|
os.Exit(15)
|
|
}
|
|
}
|
|
- bridge.Log.Debugln("Checking connection to homeserver")
|
|
|
|
- bridge.ensureConnection()
|
|
|
|
- if bridge.Crypto != nil {
|
|
|
|
- err = bridge.Crypto.Init()
|
|
|
|
- if err != nil {
|
|
|
|
- bridge.Log.Fatalln("Error initializing end-to-bridge encryption:", err)
|
|
|
|
- os.Exit(19)
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- if bridge.Provisioning != nil {
|
|
|
|
- bridge.Log.Debugln("Initializing provisioning API")
|
|
|
|
- bridge.Provisioning.Init()
|
|
|
|
|
|
+ if br.Provisioning != nil {
|
|
|
|
+ br.Log.Debugln("Initializing provisioning API")
|
|
|
|
+ br.Provisioning.Init()
|
|
}
|
|
}
|
|
- bridge.Log.Debugln("Starting application service HTTP server")
|
|
|
|
- go bridge.AS.Start()
|
|
|
|
- bridge.Log.Debugln("Starting event processor")
|
|
|
|
- go bridge.EventProcessor.Start()
|
|
|
|
- go bridge.CheckWhatsAppUpdate()
|
|
|
|
- go bridge.UpdateBotProfile()
|
|
|
|
- if bridge.Crypto != nil {
|
|
|
|
- go bridge.Crypto.Start()
|
|
|
|
- }
|
|
|
|
- go bridge.StartUsers()
|
|
|
|
- if bridge.Config.Metrics.Enabled {
|
|
|
|
- go bridge.Metrics.Start()
|
|
|
|
|
|
+ go br.CheckWhatsAppUpdate()
|
|
|
|
+ go br.StartUsers()
|
|
|
|
+ if br.Config.Metrics.Enabled {
|
|
|
|
+ go br.Metrics.Start()
|
|
}
|
|
}
|
|
|
|
|
|
- if bridge.Config.Bridge.ResendBridgeInfo {
|
|
|
|
- go bridge.ResendBridgeInfo()
|
|
|
|
|
|
+ if br.Config.Bridge.ResendBridgeInfo {
|
|
|
|
+ go br.ResendBridgeInfo()
|
|
}
|
|
}
|
|
- go bridge.Loop()
|
|
|
|
- bridge.AS.Ready = true
|
|
|
|
|
|
+ go br.Loop()
|
|
}
|
|
}
|
|
|
|
|
|
-func (bridge *Bridge) CheckWhatsAppUpdate() {
|
|
|
|
- bridge.Log.Debugfln("Checking for WhatsApp web update")
|
|
|
|
|
|
+func (br *WABridge) CheckWhatsAppUpdate() {
|
|
|
|
+ br.Log.Debugfln("Checking for WhatsApp web update")
|
|
resp, err := whatsmeow.CheckUpdate(http.DefaultClient)
|
|
resp, err := whatsmeow.CheckUpdate(http.DefaultClient)
|
|
if err != nil {
|
|
if err != nil {
|
|
- bridge.Log.Warnfln("Failed to check for WhatsApp web update: %v", err)
|
|
|
|
|
|
+ br.Log.Warnfln("Failed to check for WhatsApp web update: %v", err)
|
|
return
|
|
return
|
|
}
|
|
}
|
|
if store.GetWAVersion() == resp.ParsedVersion {
|
|
if store.GetWAVersion() == resp.ParsedVersion {
|
|
- bridge.Log.Debugfln("Bridge is using latest WhatsApp web protocol")
|
|
|
|
|
|
+ br.Log.Debugfln("Bridge is using latest WhatsApp web protocol")
|
|
} else if store.GetWAVersion().LessThan(resp.ParsedVersion) {
|
|
} else if store.GetWAVersion().LessThan(resp.ParsedVersion) {
|
|
if resp.IsBelowHard || resp.IsBroken {
|
|
if resp.IsBelowHard || resp.IsBroken {
|
|
- bridge.Log.Warnfln("Bridge is using outdated WhatsApp web protocol and probably doesn't work anymore (%s, latest is %s)", store.GetWAVersion(), resp.ParsedVersion)
|
|
|
|
|
|
+ br.Log.Warnfln("Bridge is using outdated WhatsApp web protocol and probably doesn't work anymore (%s, latest is %s)", store.GetWAVersion(), resp.ParsedVersion)
|
|
} else if resp.IsBelowSoft {
|
|
} else if resp.IsBelowSoft {
|
|
- bridge.Log.Infofln("Bridge is using outdated WhatsApp web protocol (%s, latest is %s)", store.GetWAVersion(), resp.ParsedVersion)
|
|
|
|
|
|
+ br.Log.Infofln("Bridge is using outdated WhatsApp web protocol (%s, latest is %s)", store.GetWAVersion(), resp.ParsedVersion)
|
|
} else {
|
|
} else {
|
|
- bridge.Log.Debugfln("Bridge is using outdated WhatsApp web protocol (%s, latest is %s)", store.GetWAVersion(), resp.ParsedVersion)
|
|
|
|
|
|
+ br.Log.Debugfln("Bridge is using outdated WhatsApp web protocol (%s, latest is %s)", store.GetWAVersion(), resp.ParsedVersion)
|
|
}
|
|
}
|
|
} else {
|
|
} else {
|
|
- bridge.Log.Debugfln("Bridge is using newer than latest WhatsApp web protocol")
|
|
|
|
|
|
+ br.Log.Debugfln("Bridge is using newer than latest WhatsApp web protocol")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
-func (bridge *Bridge) Loop() {
|
|
|
|
|
|
+func (br *WABridge) Loop() {
|
|
for {
|
|
for {
|
|
- bridge.SleepAndDeleteUpcoming()
|
|
|
|
|
|
+ br.SleepAndDeleteUpcoming()
|
|
time.Sleep(1 * time.Hour)
|
|
time.Sleep(1 * time.Hour)
|
|
- bridge.WarnUsersAboutDisconnection()
|
|
|
|
|
|
+ br.WarnUsersAboutDisconnection()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
-func (bridge *Bridge) WarnUsersAboutDisconnection() {
|
|
|
|
- bridge.usersLock.Lock()
|
|
|
|
- for _, user := range bridge.usersByUsername {
|
|
|
|
|
|
+func (br *WABridge) WarnUsersAboutDisconnection() {
|
|
|
|
+ br.usersLock.Lock()
|
|
|
|
+ for _, user := range br.usersByUsername {
|
|
if user.IsConnected() && !user.PhoneRecentlySeen(true) {
|
|
if user.IsConnected() && !user.PhoneRecentlySeen(true) {
|
|
go user.sendPhoneOfflineWarning()
|
|
go user.sendPhoneOfflineWarning()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
- bridge.usersLock.Unlock()
|
|
|
|
|
|
+ br.usersLock.Unlock()
|
|
}
|
|
}
|
|
|
|
|
|
-func (bridge *Bridge) ResendBridgeInfo() {
|
|
|
|
- if *dontSaveConfig {
|
|
|
|
- bridge.Log.Warnln("Not setting resend_bridge_info to false in config due to --no-update flag")
|
|
|
|
- } else {
|
|
|
|
- err := config.Mutate(*configPath, func(helper *configupgrade.Helper) {
|
|
|
|
- helper.Set(configupgrade.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")
|
|
|
|
- for _, portal := range bridge.GetAllPortals() {
|
|
|
|
- portal.UpdateBridgeInfo()
|
|
|
|
- }
|
|
|
|
- bridge.Log.Infoln("Finished re-sending bridge info state events")
|
|
|
|
|
|
+func (br *WABridge) ResendBridgeInfo() {
|
|
|
|
+ // FIXME
|
|
|
|
+ //if *dontSaveConfig {
|
|
|
|
+ // br.Log.Warnln("Not setting resend_bridge_info to false in config due to --no-update flag")
|
|
|
|
+ //} else {
|
|
|
|
+ // err := config.Mutate(*configPath, func(helper *configupgrade.Helper) {
|
|
|
|
+ // helper.Set(configupgrade.Bool, "false", "bridge", "resend_bridge_info")
|
|
|
|
+ // })
|
|
|
|
+ // if err != nil {
|
|
|
|
+ // br.Log.Errorln("Failed to save config after setting resend_bridge_info to false:", err)
|
|
|
|
+ // }
|
|
|
|
+ //}
|
|
|
|
+ //br.Log.Infoln("Re-sending bridge info state event to all portals")
|
|
|
|
+ //for _, portal := range br.GetAllPortals() {
|
|
|
|
+ // portal.UpdateBridgeInfo()
|
|
|
|
+ //}
|
|
|
|
+ //br.Log.Infoln("Finished re-sending bridge info state events")
|
|
}
|
|
}
|
|
|
|
|
|
-func (bridge *Bridge) UpdateBotProfile() {
|
|
|
|
- bridge.Log.Debugln("Updating bot profile")
|
|
|
|
- botConfig := &bridge.Config.AppService.Bot
|
|
|
|
-
|
|
|
|
- var err error
|
|
|
|
- var mxc id.ContentURI
|
|
|
|
- if botConfig.Avatar == "remove" {
|
|
|
|
- err = bridge.Bot.SetAvatarURL(mxc)
|
|
|
|
- } else if len(botConfig.Avatar) > 0 {
|
|
|
|
- mxc, err = id.ParseContentURI(botConfig.Avatar)
|
|
|
|
- if err == nil {
|
|
|
|
- err = bridge.Bot.SetAvatarURL(mxc)
|
|
|
|
- }
|
|
|
|
- botConfig.ParsedAvatar = mxc
|
|
|
|
- }
|
|
|
|
- if err != nil {
|
|
|
|
- bridge.Log.Warnln("Failed to update bot avatar:", err)
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if botConfig.Displayname == "remove" {
|
|
|
|
- err = bridge.Bot.SetDisplayName("")
|
|
|
|
- } else if len(botConfig.Displayname) > 0 {
|
|
|
|
- err = bridge.Bot.SetDisplayName(botConfig.Displayname)
|
|
|
|
- }
|
|
|
|
- if err != nil {
|
|
|
|
- bridge.Log.Warnln("Failed to update bot displayname:", err)
|
|
|
|
- }
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-func (bridge *Bridge) StartUsers() {
|
|
|
|
- bridge.Log.Debugln("Starting users")
|
|
|
|
|
|
+func (br *WABridge) StartUsers() {
|
|
|
|
+ br.Log.Debugln("Starting users")
|
|
foundAnySessions := false
|
|
foundAnySessions := false
|
|
- for _, user := range bridge.GetAllUsers() {
|
|
|
|
|
|
+ for _, user := range br.GetAllUsers() {
|
|
if !user.JID.IsEmpty() {
|
|
if !user.JID.IsEmpty() {
|
|
foundAnySessions = true
|
|
foundAnySessions = true
|
|
}
|
|
}
|
|
go user.Connect()
|
|
go user.Connect()
|
|
}
|
|
}
|
|
if !foundAnySessions {
|
|
if !foundAnySessions {
|
|
- bridge.sendGlobalBridgeState(BridgeState{StateEvent: StateUnconfigured}.fill(nil))
|
|
|
|
|
|
+ br.sendGlobalBridgeState(BridgeState{StateEvent: StateUnconfigured}.fill(nil))
|
|
}
|
|
}
|
|
- bridge.Log.Debugln("Starting custom puppets")
|
|
|
|
- for _, loopuppet := range bridge.GetAllPuppetsWithCustomMXID() {
|
|
|
|
|
|
+ br.Log.Debugln("Starting custom puppets")
|
|
|
|
+ for _, loopuppet := range br.GetAllPuppetsWithCustomMXID() {
|
|
go func(puppet *Puppet) {
|
|
go func(puppet *Puppet) {
|
|
puppet.log.Debugln("Starting custom puppet", puppet.CustomMXID)
|
|
puppet.log.Debugln("Starting custom puppet", puppet.CustomMXID)
|
|
err := puppet.StartCustomMXID(true)
|
|
err := puppet.StartCustomMXID(true)
|
|
@@ -473,80 +222,37 @@ func (bridge *Bridge) StartUsers() {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
-func (bridge *Bridge) Stop() {
|
|
|
|
- if bridge.Crypto != nil {
|
|
|
|
- bridge.Crypto.Stop()
|
|
|
|
|
|
+func (br *WABridge) Stop() {
|
|
|
|
+ if br.Crypto != nil {
|
|
|
|
+ br.Crypto.Stop()
|
|
}
|
|
}
|
|
- bridge.AS.Stop()
|
|
|
|
- bridge.Metrics.Stop()
|
|
|
|
- bridge.EventProcessor.Stop()
|
|
|
|
- for _, user := range bridge.usersByUsername {
|
|
|
|
|
|
+ br.AS.Stop()
|
|
|
|
+ br.Metrics.Stop()
|
|
|
|
+ br.EventProcessor.Stop()
|
|
|
|
+ for _, user := range br.usersByUsername {
|
|
if user.Client == nil {
|
|
if user.Client == nil {
|
|
continue
|
|
continue
|
|
}
|
|
}
|
|
- bridge.Log.Debugln("Disconnecting", user.MXID)
|
|
|
|
|
|
+ br.Log.Debugln("Disconnecting", user.MXID)
|
|
user.Client.Disconnect()
|
|
user.Client.Disconnect()
|
|
close(user.historySyncs)
|
|
close(user.historySyncs)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
-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)
|
|
|
|
- }
|
|
|
|
|
|
+func (br *WABridge) GetExampleConfig() string {
|
|
|
|
+ return ExampleConfig
|
|
|
|
+}
|
|
|
|
|
|
- if *generateRegistration {
|
|
|
|
- bridge.GenerateRegistration()
|
|
|
|
- return
|
|
|
|
- } else if *migrateFrom {
|
|
|
|
- bridge.MigrateDatabase()
|
|
|
|
- return
|
|
|
|
|
|
+func (br *WABridge) GetConfigPtr() interface{} {
|
|
|
|
+ br.Config = &config.Config{
|
|
|
|
+ BaseConfig: &br.Bridge.Config,
|
|
}
|
|
}
|
|
-
|
|
|
|
- bridge.Init()
|
|
|
|
- bridge.Log.Infoln("Bridge initialization complete, starting...")
|
|
|
|
- bridge.Start()
|
|
|
|
- bridge.Log.Infoln("Bridge started!")
|
|
|
|
-
|
|
|
|
- c := make(chan os.Signal)
|
|
|
|
- signal.Notify(c, os.Interrupt, syscall.SIGTERM)
|
|
|
|
- <-c
|
|
|
|
-
|
|
|
|
- bridge.Log.Infoln("Interrupt received, stopping...")
|
|
|
|
- bridge.Stop()
|
|
|
|
- bridge.Log.Infoln("Bridge stopped.")
|
|
|
|
- os.Exit(0)
|
|
|
|
|
|
+ br.Config.BaseConfig.Bridge = &br.Config.Bridge
|
|
|
|
+ return br.Config
|
|
}
|
|
}
|
|
|
|
|
|
func main() {
|
|
func main() {
|
|
- flag.SetHelpTitles(
|
|
|
|
- "mautrix-whatsapp - A Matrix-WhatsApp puppeting bridge.",
|
|
|
|
- "mautrix-whatsapp [-h] [-c <path>] [-r <path>] [-g] [--migrate-db <source type> <source uri>]")
|
|
|
|
- err := flag.Parse()
|
|
|
|
- if err != nil {
|
|
|
|
- _, _ = fmt.Fprintln(os.Stderr, err)
|
|
|
|
- flag.PrintHelp()
|
|
|
|
- os.Exit(1)
|
|
|
|
- } else if *wantHelp {
|
|
|
|
- flag.PrintHelp()
|
|
|
|
- os.Exit(0)
|
|
|
|
- } else if *version {
|
|
|
|
- fmt.Println(VersionString)
|
|
|
|
- return
|
|
|
|
- }
|
|
|
|
- upgrades.IgnoreForeignTables = *ignoreForeignTables
|
|
|
|
-
|
|
|
|
- (&Bridge{
|
|
|
|
|
|
+ br := &WABridge{
|
|
usersByMXID: make(map[id.UserID]*User),
|
|
usersByMXID: make(map[id.UserID]*User),
|
|
usersByUsername: make(map[string]*User),
|
|
usersByUsername: make(map[string]*User),
|
|
spaceRooms: make(map[id.RoomID]*User),
|
|
spaceRooms: make(map[id.RoomID]*User),
|
|
@@ -555,5 +261,24 @@ func main() {
|
|
portalsByJID: make(map[database.PortalKey]*Portal),
|
|
portalsByJID: make(map[database.PortalKey]*Portal),
|
|
puppets: make(map[types.JID]*Puppet),
|
|
puppets: make(map[types.JID]*Puppet),
|
|
puppetsByCustomMXID: make(map[id.UserID]*Puppet),
|
|
puppetsByCustomMXID: make(map[id.UserID]*Puppet),
|
|
- }).Main()
|
|
|
|
|
|
+ }
|
|
|
|
+ br.Bridge = bridge.Bridge{
|
|
|
|
+ Name: "mautrix-whatsapp",
|
|
|
|
+ URL: "https://github.com/mautrix/whatsapp",
|
|
|
|
+ Description: "A Matrix-WhatsApp puppeting bridge.",
|
|
|
|
+ Version: "0.4.0",
|
|
|
|
+ ProtocolName: "WhatsApp",
|
|
|
|
+
|
|
|
|
+ ConfigUpgrader: &configupgrade.StructUpgrader{
|
|
|
|
+ SimpleUpgrader: configupgrade.SimpleUpgrader(config.DoUpgrade),
|
|
|
|
+ Blocks: config.SpacedBlocks,
|
|
|
|
+ Base: ExampleConfig,
|
|
|
|
+ },
|
|
|
|
+
|
|
|
|
+ Child: br,
|
|
|
|
+ }
|
|
|
|
+ br.InitVersion(Tag, Commit, BuildTime)
|
|
|
|
+ br.WAVersion = strings.FieldsFunc(br.Version, func(r rune) bool { return r == '-' || r == '+' })[0]
|
|
|
|
+
|
|
|
|
+ br.Main()
|
|
}
|
|
}
|