2018-08-13 22:24:44 +02:00
|
|
|
// mautrix-whatsapp - A Matrix-WhatsApp puppeting bridge.
|
2022-02-23 13:30:21 +01:00
|
|
|
// Copyright (C) 2022 Tulir Asokan
|
2018-08-12 21:26:05 +02:00
|
|
|
//
|
|
|
|
// 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 main
|
|
|
|
|
2018-08-13 00:00:23 +02:00
|
|
|
import (
|
2024-03-11 21:27:10 +01:00
|
|
|
"context"
|
2021-10-31 19:42:53 +01:00
|
|
|
_ "embed"
|
2022-03-12 19:05:57 +01:00
|
|
|
"net/http"
|
2023-09-29 13:19:48 +02:00
|
|
|
"net/url"
|
2018-08-13 00:00:23 +02:00
|
|
|
"os"
|
2021-10-22 19:14:34 +02:00
|
|
|
"strconv"
|
2020-06-03 19:32:53 +02:00
|
|
|
"strings"
|
2018-08-28 23:40:54 +02:00
|
|
|
"sync"
|
2019-11-10 20:22:11 +01:00
|
|
|
"time"
|
2018-08-26 21:53:13 +02:00
|
|
|
|
2024-03-11 21:27:10 +01:00
|
|
|
"github.com/rs/zerolog"
|
|
|
|
waLog "go.mau.fi/whatsmeow/util/log"
|
2022-05-23 23:08:28 +02:00
|
|
|
"google.golang.org/protobuf/proto"
|
|
|
|
|
2022-03-12 19:05:57 +01:00
|
|
|
"go.mau.fi/whatsmeow"
|
2021-10-22 19:14:34 +02:00
|
|
|
waProto "go.mau.fi/whatsmeow/binary/proto"
|
|
|
|
"go.mau.fi/whatsmeow/store"
|
|
|
|
"go.mau.fi/whatsmeow/store/sqlstore"
|
|
|
|
"go.mau.fi/whatsmeow/types"
|
2021-02-17 00:21:30 +01:00
|
|
|
|
2024-03-11 21:27:10 +01:00
|
|
|
"go.mau.fi/util/configupgrade"
|
|
|
|
|
2022-05-22 00:06:30 +02:00
|
|
|
"maunium.net/go/mautrix/bridge"
|
2022-05-22 15:15:54 +02:00
|
|
|
"maunium.net/go/mautrix/bridge/commands"
|
2022-08-15 15:36:28 +02:00
|
|
|
"maunium.net/go/mautrix/bridge/status"
|
2022-05-22 15:15:54 +02:00
|
|
|
"maunium.net/go/mautrix/event"
|
2020-05-08 21:32:22 +02:00
|
|
|
"maunium.net/go/mautrix/id"
|
2019-01-11 20:17:31 +01:00
|
|
|
|
2018-08-26 21:53:13 +02:00
|
|
|
"maunium.net/go/mautrix-whatsapp/config"
|
2018-08-13 22:24:44 +02:00
|
|
|
"maunium.net/go/mautrix-whatsapp/database"
|
2021-10-28 12:57:15 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
// Information to find out exactly which commit the bridge was built from.
|
|
|
|
// These are filled at build time with the -X linker flag.
|
|
|
|
var (
|
2020-06-03 19:59:44 +02:00
|
|
|
Tag = "unknown"
|
|
|
|
Commit = "unknown"
|
2020-06-03 19:32:53 +02:00
|
|
|
BuildTime = "unknown"
|
|
|
|
)
|
|
|
|
|
2021-10-31 12:04:44 +01:00
|
|
|
//go:embed example-config.yaml
|
|
|
|
var ExampleConfig string
|
|
|
|
|
2022-05-22 00:06:30 +02:00
|
|
|
type WABridge struct {
|
|
|
|
bridge.Bridge
|
2022-05-22 15:15:54 +02:00
|
|
|
Config *config.Config
|
|
|
|
DB *database.Database
|
|
|
|
Provisioning *ProvisioningAPI
|
|
|
|
Formatter *Formatter
|
|
|
|
Metrics *MetricsHandler
|
|
|
|
WAContainer *sqlstore.Container
|
|
|
|
WAVersion string
|
2018-08-28 23:40:54 +02:00
|
|
|
|
2020-05-08 21:32:22 +02:00
|
|
|
usersByMXID map[id.UserID]*User
|
2021-10-22 19:14:34 +02:00
|
|
|
usersByUsername map[string]*User
|
2018-08-28 23:40:54 +02:00
|
|
|
usersLock sync.Mutex
|
2021-12-28 21:14:09 +01:00
|
|
|
spaceRooms map[id.RoomID]*User
|
2021-12-29 13:19:16 +01:00
|
|
|
spaceRoomsLock sync.Mutex
|
2021-12-28 21:14:09 +01:00
|
|
|
managementRooms map[id.RoomID]*User
|
2021-12-29 13:19:16 +01:00
|
|
|
managementRoomsLock sync.Mutex
|
2020-05-08 21:32:22 +02:00
|
|
|
portalsByMXID map[id.RoomID]*Portal
|
2018-08-28 23:40:54 +02:00
|
|
|
portalsByJID map[database.PortalKey]*Portal
|
|
|
|
portalsLock sync.Mutex
|
2021-10-22 19:14:34 +02:00
|
|
|
puppets map[types.JID]*Puppet
|
2020-05-08 21:32:22 +02:00
|
|
|
puppetsByCustomMXID map[id.UserID]*Puppet
|
2018-08-28 23:40:54 +02:00
|
|
|
puppetsLock sync.Mutex
|
2018-08-13 22:24:44 +02:00
|
|
|
}
|
|
|
|
|
2022-05-22 00:06:30 +02:00
|
|
|
func (br *WABridge) Init() {
|
2022-05-22 15:15:54 +02:00
|
|
|
br.CommandProcessor = commands.NewProcessor(&br.Bridge)
|
|
|
|
br.RegisterCommands()
|
|
|
|
|
|
|
|
// TODO this is a weird place for this
|
|
|
|
br.EventProcessor.On(event.EphemeralEventPresence, br.HandlePresence)
|
2022-12-23 14:17:57 +01:00
|
|
|
br.EventProcessor.On(TypeMSC3381PollStart, br.MatrixHandler.HandleMessage)
|
|
|
|
br.EventProcessor.On(TypeMSC3381PollResponse, br.MatrixHandler.HandleMessage)
|
|
|
|
br.EventProcessor.On(TypeMSC3381V2PollResponse, br.MatrixHandler.HandleMessage)
|
2022-05-22 15:15:54 +02:00
|
|
|
|
2024-03-11 21:27:10 +01:00
|
|
|
Analytics.log = br.ZLog.With().Str("component", "analytics").Logger()
|
2023-09-29 13:19:48 +02:00
|
|
|
Analytics.url = (&url.URL{
|
|
|
|
Scheme: "https",
|
|
|
|
Host: br.Config.Analytics.Host,
|
|
|
|
Path: "/v1/track",
|
|
|
|
}).String()
|
|
|
|
Analytics.key = br.Config.Analytics.Token
|
|
|
|
Analytics.userID = br.Config.Analytics.UserID
|
|
|
|
if Analytics.IsEnabled() {
|
2024-03-11 21:27:10 +01:00
|
|
|
Analytics.log.Info().Str("override_user_id", Analytics.userID).Msg("Analytics metrics are enabled")
|
2022-05-16 12:46:18 +02:00
|
|
|
}
|
|
|
|
|
2024-03-11 21:27:10 +01:00
|
|
|
br.DB = database.New(br.Bridge.DB)
|
|
|
|
br.WAContainer = sqlstore.NewWithDB(br.DB.RawDB, br.DB.Dialect.String(), waLog.Zerolog(br.ZLog.With().Str("db_section", "whatsmeow").Logger()))
|
2022-05-22 00:06:30 +02:00
|
|
|
br.WAContainer.DatabaseErrorHandler = br.DB.HandleSignalStoreError
|
2021-10-22 19:14:34 +02:00
|
|
|
|
2022-05-22 00:06:30 +02:00
|
|
|
ss := br.Config.Bridge.Provisioning.SharedSecret
|
2020-02-09 19:32:14 +01:00
|
|
|
if len(ss) > 0 && ss != "disable" {
|
2024-03-11 21:27:10 +01:00
|
|
|
br.Provisioning = &ProvisioningAPI{bridge: br, log: br.ZLog.With().Str("component", "provisioning").Logger()}
|
2020-02-09 19:32:14 +01:00
|
|
|
}
|
|
|
|
|
2022-05-22 00:06:30 +02:00
|
|
|
br.Formatter = NewFormatter(br)
|
2024-03-11 21:27:10 +01:00
|
|
|
br.Metrics = NewMetricsHandler(br.Config.Metrics.Listen, br.ZLog.With().Str("component", "metrics").Logger(), br.DB)
|
2022-05-22 16:32:22 +02:00
|
|
|
br.MatrixHandler.TrackEventDuration = br.Metrics.TrackMatrixEvent
|
2021-10-22 19:14:34 +02:00
|
|
|
|
2022-05-22 00:06:30 +02:00
|
|
|
store.BaseClientPayload.UserAgent.OsVersion = proto.String(br.WAVersion)
|
|
|
|
store.BaseClientPayload.UserAgent.OsBuildNumber = proto.String(br.WAVersion)
|
2022-05-23 23:08:28 +02:00
|
|
|
store.DeviceProps.Os = proto.String(br.Config.WhatsApp.OSName)
|
|
|
|
store.DeviceProps.RequireFullSync = proto.Bool(br.Config.Bridge.HistorySync.RequestFullSync)
|
2023-01-04 21:37:25 +01:00
|
|
|
if fsc := br.Config.Bridge.HistorySync.FullSyncConfig; fsc.DaysLimit > 0 && fsc.SizeLimit > 0 && fsc.StorageQuota > 0 {
|
|
|
|
store.DeviceProps.HistorySyncConfig = &waProto.DeviceProps_HistorySyncConfig{
|
|
|
|
FullSyncDaysLimit: proto.Uint32(fsc.DaysLimit),
|
|
|
|
FullSyncSizeMbLimit: proto.Uint32(fsc.SizeLimit),
|
|
|
|
StorageQuotaMb: proto.Uint32(fsc.StorageQuota),
|
|
|
|
}
|
|
|
|
}
|
2022-05-22 00:06:30 +02:00
|
|
|
versionParts := strings.Split(br.WAVersion, ".")
|
2021-10-22 19:14:34 +02:00
|
|
|
if len(versionParts) > 2 {
|
|
|
|
primary, _ := strconv.Atoi(versionParts[0])
|
|
|
|
secondary, _ := strconv.Atoi(versionParts[1])
|
|
|
|
tertiary, _ := strconv.Atoi(versionParts[2])
|
2022-05-23 23:08:28 +02:00
|
|
|
store.DeviceProps.Version.Primary = proto.Uint32(uint32(primary))
|
|
|
|
store.DeviceProps.Version.Secondary = proto.Uint32(uint32(secondary))
|
|
|
|
store.DeviceProps.Version.Tertiary = proto.Uint32(uint32(tertiary))
|
2021-10-22 19:14:34 +02:00
|
|
|
}
|
2022-07-30 10:30:44 +02:00
|
|
|
platformID, ok := waProto.DeviceProps_PlatformType_value[strings.ToUpper(br.Config.WhatsApp.BrowserName)]
|
2021-10-22 19:14:34 +02:00
|
|
|
if ok {
|
2022-07-30 10:30:44 +02:00
|
|
|
store.DeviceProps.PlatformType = waProto.DeviceProps_PlatformType(platformID).Enum()
|
2021-10-22 19:14:34 +02:00
|
|
|
}
|
2018-08-13 22:24:44 +02:00
|
|
|
}
|
|
|
|
|
2022-05-22 00:06:30 +02:00
|
|
|
func (br *WABridge) Start() {
|
|
|
|
err := br.WAContainer.Upgrade()
|
|
|
|
if err != nil {
|
2024-03-11 21:27:10 +01:00
|
|
|
br.ZLog.WithLevel(zerolog.FatalLevel).Err(err).Msg("Failed to upgrade whatsmeow database")
|
2018-08-29 22:48:15 +02:00
|
|
|
os.Exit(15)
|
2018-08-18 21:57:08 +02:00
|
|
|
}
|
2022-05-22 00:06:30 +02:00
|
|
|
if br.Provisioning != nil {
|
|
|
|
br.Provisioning.Init()
|
2020-02-09 19:32:14 +01:00
|
|
|
}
|
2022-05-22 00:06:30 +02:00
|
|
|
go br.CheckWhatsAppUpdate()
|
2023-06-09 14:44:29 +02:00
|
|
|
br.WaitWebsocketConnected()
|
2022-05-22 00:06:30 +02:00
|
|
|
go br.StartUsers()
|
|
|
|
if br.Config.Metrics.Enabled {
|
|
|
|
go br.Metrics.Start()
|
2020-06-17 16:50:06 +02:00
|
|
|
}
|
2020-06-15 19:28:04 +02:00
|
|
|
|
2022-05-22 00:06:30 +02:00
|
|
|
go br.Loop()
|
2020-06-15 19:28:04 +02:00
|
|
|
}
|
|
|
|
|
2022-05-22 00:06:30 +02:00
|
|
|
func (br *WABridge) CheckWhatsAppUpdate() {
|
2024-03-11 21:27:10 +01:00
|
|
|
br.ZLog.Debug().Msg("Checking for WhatsApp web update")
|
2022-03-12 19:05:57 +01:00
|
|
|
resp, err := whatsmeow.CheckUpdate(http.DefaultClient)
|
|
|
|
if err != nil {
|
2024-03-11 21:27:10 +01:00
|
|
|
br.ZLog.Warn().Err(err).Msg("Failed to check for WhatsApp web update")
|
2022-03-12 19:05:57 +01:00
|
|
|
return
|
|
|
|
}
|
|
|
|
if store.GetWAVersion() == resp.ParsedVersion {
|
2024-03-11 21:27:10 +01:00
|
|
|
br.ZLog.Debug().Msg("Bridge is using latest WhatsApp web protocol")
|
2022-03-12 19:05:57 +01:00
|
|
|
} else if store.GetWAVersion().LessThan(resp.ParsedVersion) {
|
2022-03-23 13:46:30 +01:00
|
|
|
if resp.IsBelowHard || resp.IsBroken {
|
2024-03-11 21:27:10 +01:00
|
|
|
br.ZLog.Warn().
|
|
|
|
Stringer("latest_version", resp.ParsedVersion).
|
|
|
|
Stringer("current_version", store.GetWAVersion()).
|
|
|
|
Msg("Bridge is using outdated WhatsApp web protocol and probably doesn't work anymore")
|
2022-03-23 13:46:30 +01:00
|
|
|
} else if resp.IsBelowSoft {
|
2024-03-11 21:27:10 +01:00
|
|
|
br.ZLog.Info().
|
|
|
|
Stringer("latest_version", resp.ParsedVersion).
|
|
|
|
Stringer("current_version", store.GetWAVersion()).
|
|
|
|
Msg("Bridge is using outdated WhatsApp web protocol")
|
2022-03-12 19:05:57 +01:00
|
|
|
} else {
|
2024-03-11 21:27:10 +01:00
|
|
|
br.ZLog.Debug().
|
|
|
|
Stringer("latest_version", resp.ParsedVersion).
|
|
|
|
Stringer("current_version", store.GetWAVersion()).
|
|
|
|
Msg("Bridge is using outdated WhatsApp web protocol")
|
2022-03-12 19:05:57 +01:00
|
|
|
}
|
|
|
|
} else {
|
2024-03-11 21:27:10 +01:00
|
|
|
br.ZLog.Debug().Msg("Bridge is using newer than latest WhatsApp web protocol")
|
2022-03-12 19:05:57 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-22 00:06:30 +02:00
|
|
|
func (br *WABridge) Loop() {
|
2024-03-11 21:27:10 +01:00
|
|
|
ctx := br.ZLog.With().Str("action", "background loop").Logger().WithContext(context.TODO())
|
2022-01-25 13:26:24 +01:00
|
|
|
for {
|
2024-03-11 21:27:10 +01:00
|
|
|
br.SleepAndDeleteUpcoming(ctx)
|
2022-01-25 13:26:24 +01:00
|
|
|
time.Sleep(1 * time.Hour)
|
2022-05-22 00:06:30 +02:00
|
|
|
br.WarnUsersAboutDisconnection()
|
2022-01-25 13:26:24 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-22 00:06:30 +02:00
|
|
|
func (br *WABridge) WarnUsersAboutDisconnection() {
|
|
|
|
br.usersLock.Lock()
|
|
|
|
for _, user := range br.usersByUsername {
|
2022-02-18 11:12:15 +01:00
|
|
|
if user.IsConnected() && !user.PhoneRecentlySeen(true) {
|
2024-03-11 21:27:10 +01:00
|
|
|
go user.sendPhoneOfflineWarning(context.TODO())
|
2022-01-25 13:26:24 +01:00
|
|
|
}
|
|
|
|
}
|
2022-05-22 00:06:30 +02:00
|
|
|
br.usersLock.Unlock()
|
2022-01-25 13:26:24 +01:00
|
|
|
}
|
|
|
|
|
2022-05-22 00:06:30 +02:00
|
|
|
func (br *WABridge) StartUsers() {
|
2024-03-11 21:27:10 +01:00
|
|
|
br.ZLog.Debug().Msg("Starting users")
|
2021-08-04 15:14:26 +02:00
|
|
|
foundAnySessions := false
|
2022-05-22 00:06:30 +02:00
|
|
|
for _, user := range br.GetAllUsers() {
|
2021-10-22 19:14:34 +02:00
|
|
|
if !user.JID.IsEmpty() {
|
2021-08-04 15:14:26 +02:00
|
|
|
foundAnySessions = true
|
|
|
|
}
|
2021-10-27 14:54:34 +02:00
|
|
|
go user.Connect()
|
2018-08-18 21:57:08 +02:00
|
|
|
}
|
2021-08-04 15:14:26 +02:00
|
|
|
if !foundAnySessions {
|
2022-08-15 15:36:28 +02:00
|
|
|
br.SendGlobalBridgeState(status.BridgeState{StateEvent: status.StateUnconfigured}.Fill(nil))
|
2021-08-04 15:14:26 +02:00
|
|
|
}
|
2024-03-11 21:27:10 +01:00
|
|
|
br.ZLog.Debug().Msg("Starting custom puppets")
|
2022-05-22 00:06:30 +02:00
|
|
|
for _, loopuppet := range br.GetAllPuppetsWithCustomMXID() {
|
2019-05-31 22:07:33 +02:00
|
|
|
go func(puppet *Puppet) {
|
2024-03-11 21:27:10 +01:00
|
|
|
puppet.zlog.Debug().Stringer("custom_mxid", puppet.CustomMXID).Msg("Starting double puppet")
|
2021-02-11 19:47:10 +01:00
|
|
|
err := puppet.StartCustomMXID(true)
|
2019-05-24 01:33:26 +02:00
|
|
|
if err != nil {
|
2024-03-11 21:27:10 +01:00
|
|
|
puppet.zlog.Err(err).Stringer("custom_mxid", puppet.CustomMXID).Msg("Failed to start double puppet")
|
2019-05-24 01:33:26 +02:00
|
|
|
}
|
2019-05-31 22:07:33 +02:00
|
|
|
}(loopuppet)
|
2019-05-24 01:33:26 +02:00
|
|
|
}
|
2018-08-18 21:57:08 +02:00
|
|
|
}
|
|
|
|
|
2022-05-22 00:06:30 +02:00
|
|
|
func (br *WABridge) Stop() {
|
|
|
|
br.Metrics.Stop()
|
|
|
|
for _, user := range br.usersByUsername {
|
2021-10-22 19:14:34 +02:00
|
|
|
if user.Client == nil {
|
2019-05-23 21:57:19 +02:00
|
|
|
continue
|
|
|
|
}
|
2024-03-11 21:27:10 +01:00
|
|
|
user.zlog.Debug().Msg("Disconnecting user")
|
2021-10-22 19:14:34 +02:00
|
|
|
user.Client.Disconnect()
|
2021-10-26 16:01:10 +02:00
|
|
|
close(user.historySyncs)
|
2019-05-23 18:16:29 +02:00
|
|
|
}
|
2018-08-13 22:24:44 +02:00
|
|
|
}
|
|
|
|
|
2022-05-22 00:06:30 +02:00
|
|
|
func (br *WABridge) GetExampleConfig() string {
|
|
|
|
return ExampleConfig
|
|
|
|
}
|
2021-11-07 21:31:22 +01:00
|
|
|
|
2022-05-22 00:06:30 +02:00
|
|
|
func (br *WABridge) GetConfigPtr() interface{} {
|
|
|
|
br.Config = &config.Config{
|
|
|
|
BaseConfig: &br.Bridge.Config,
|
2018-08-13 22:24:44 +02:00
|
|
|
}
|
2022-05-22 00:06:30 +02:00
|
|
|
br.Config.BaseConfig.Bridge = &br.Config.Bridge
|
|
|
|
return br.Config
|
2018-08-13 22:24:44 +02:00
|
|
|
}
|
|
|
|
|
2018-08-12 21:26:05 +02:00
|
|
|
func main() {
|
2022-05-22 00:06:30 +02:00
|
|
|
br := &WABridge{
|
2021-11-07 21:31:22 +01:00
|
|
|
usersByMXID: make(map[id.UserID]*User),
|
|
|
|
usersByUsername: make(map[string]*User),
|
2021-12-29 13:19:16 +01:00
|
|
|
spaceRooms: make(map[id.RoomID]*User),
|
2021-11-07 21:31:22 +01:00
|
|
|
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),
|
2022-05-22 00:06:30 +02:00
|
|
|
}
|
|
|
|
br.Bridge = bridge.Bridge{
|
2023-04-10 17:39:05 +02:00
|
|
|
Name: "mautrix-whatsapp",
|
|
|
|
URL: "https://github.com/mautrix/whatsapp",
|
|
|
|
Description: "A Matrix-WhatsApp puppeting bridge.",
|
2023-12-16 22:37:12 +01:00
|
|
|
Version: "0.10.5",
|
2023-04-10 17:39:05 +02:00
|
|
|
ProtocolName: "WhatsApp",
|
|
|
|
BeeperServiceName: "whatsapp",
|
|
|
|
BeeperNetworkName: "whatsapp",
|
2022-05-22 00:06:30 +02:00
|
|
|
|
2022-05-31 16:28:58 +02:00
|
|
|
CryptoPickleKey: "maunium.net/go/mautrix-whatsapp",
|
|
|
|
|
2022-05-22 00:06:30 +02:00
|
|
|
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()
|
2018-08-13 22:24:44 +02:00
|
|
|
}
|