merged changes to latest version of mautrix/whatsapp

This commit is contained in:
Gabriel Garcia Leyva 2025-03-12 15:48:02 +04:00
parent 7319d429d7
commit cceb4b8f1c
8 changed files with 246 additions and 8 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

15
go.mod
View file

@ -9,9 +9,10 @@ require (
github.com/gorilla/websocket v1.5.0
github.com/lib/pq v1.10.9
github.com/rs/zerolog v1.33.0
github.com/tidwall/gjson v1.18.0
go.mau.fi/util v0.8.6-0.20250227184636-7ff63b0b9d95
go.mau.fi/webp v0.2.0
go.mau.fi/whatsmeow v0.0.0-20250311112832-01523b1e7109
go.mau.fi/whatsmeow v0.0.0-20250307203951-daf102be9698
golang.org/x/image v0.24.0
golang.org/x/net v0.35.0
golang.org/x/sync v0.11.0
@ -23,12 +24,16 @@ require (
require (
filippo.io/edwards25519 v1.1.0 // indirect
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-sqlite3 v1.14.24 // indirect
github.com/mdp/qrterminal/v3 v3.2.0 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/petermattis/goid v0.0.0-20250211185408-f2b9d978cd7a // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rogpeppe/go-internal v1.10.0 // indirect
github.com/rs/xid v1.6.0 // indirect
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect
@ -42,8 +47,16 @@ require (
golang.org/x/crypto v0.33.0 // indirect
golang.org/x/exp v0.0.0-20250215185904-eff6e970281f // indirect
golang.org/x/sys v0.30.0 // indirect
golang.org/x/term v0.29.0 // indirect
golang.org/x/text v0.22.0 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
maunium.net/go/mauflag v1.0.0 // indirect
modernc.org/libc v1.61.13 // indirect
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.8.2 // indirect
modernc.org/sqlite v1.36.0 // indirect
rsc.io/qr v0.2.0 // indirect
)
replace maunium.net/go/mautrix => /build/go

28
go.sum
View file

@ -7,6 +7,8 @@ github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
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/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
@ -34,12 +36,18 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/mdp/qrterminal/v3 v3.2.0 h1:qteQMXO3oyTK4IHwj2mWsKYYRBOp1Pj2WRYFYYNTCdk=
github.com/mdp/qrterminal/v3 v3.2.0/go.mod h1:XGGuua4Lefrl7TLEsSONiD+UEjQXJZ4mPzF+gWYIJkk=
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/petermattis/goid v0.0.0-20250211185408-f2b9d978cd7a h1:ckxP/kGzsxvxXo8jO6E/0QJ8MMmwI7IRj4Fys9QbAZA=
github.com/petermattis/goid v0.0.0-20250211185408-f2b9d978cd7a/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
@ -70,8 +78,10 @@ go.mau.fi/util v0.8.6-0.20250227184636-7ff63b0b9d95 h1:5EfVWWjU2Hte9uE6B/hBgvjnV
go.mau.fi/util v0.8.6-0.20250227184636-7ff63b0b9d95/go.mod h1:Ycug9mrbztlahHPEJ6H5r8Nu/xqZaWbE5vPHVWmfz6M=
go.mau.fi/webp v0.2.0 h1:QVMenHw7JDb4vall5sV75JNBQj9Hw4u8AKbi1QetHvg=
go.mau.fi/webp v0.2.0/go.mod h1:VSg9MyODn12Mb5pyG0NIyNFhujrmoFSsZBs8syOZD1Q=
go.mau.fi/whatsmeow v0.0.0-20250311112832-01523b1e7109 h1:/B6T0f6dmPKmld8uuZEhDTINJltxSBrUYA4ECsUQ9pE=
go.mau.fi/whatsmeow v0.0.0-20250311112832-01523b1e7109/go.mod h1:6hRrUtDWI2wTRClOd6m17GwrFE2a8/p5R4pjJsIVn+U=
go.mau.fi/whatsmeow v0.0.0-20250306135213-ae5c492c5067 h1:ScfoS96E8XvYp/FWHK+iN2TCifR5gTbkkltQ9bYDCRQ=
go.mau.fi/whatsmeow v0.0.0-20250306135213-ae5c492c5067/go.mod h1:6hRrUtDWI2wTRClOd6m17GwrFE2a8/p5R4pjJsIVn+U=
go.mau.fi/whatsmeow v0.0.0-20250307203951-daf102be9698 h1:JRng1Qa5ZyOx59Cprle+DNf8LN0MAT2WJZis38hwuHQ=
go.mau.fi/whatsmeow v0.0.0-20250307203951-daf102be9698/go.mod h1:6hRrUtDWI2wTRClOd6m17GwrFE2a8/p5R4pjJsIVn+U=
go.mau.fi/zeroconfig v0.1.3 h1:As9wYDKmktjmNZW5i1vn8zvJlmGKHeVxHVIBMXsm4kM=
go.mau.fi/zeroconfig v0.1.3/go.mod h1:NcSJkf180JT+1IId76PcMuLTNa1CzsFFZ0nBygIQM70=
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
@ -89,6 +99,8 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
@ -102,5 +114,13 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
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/mautrix v0.23.2-0.20250311105500-a6d948f7c2bb h1:HAKsPqJiBsugs8qOy9mMmASEpmqpJ/Cbc/2Bj6wuNYQ=
maunium.net/go/mautrix v0.23.2-0.20250311105500-a6d948f7c2bb/go.mod h1:IHMaSJh7YIxMrZSDVefS+nLdr3RbeLowsCSa6ibONZ0=
modernc.org/libc v1.61.13 h1:3LRd6ZO1ezsFiX1y+bHd1ipyEHIJKvuprv0sLTBwLW8=
modernc.org/libc v1.61.13/go.mod h1:8F/uJWL/3nNil0Lgt1Dpz+GgkApWh04N3el3hxJcA6E=
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
modernc.org/memory v1.8.2 h1:cL9L4bcoAObu4NkxOlKWBWtNHIsnnACGF/TbqQ6sbcI=
modernc.org/memory v1.8.2/go.mod h1:ZbjSvMO5NQ1A2i3bWeDiVMxIorXwdClKE/0SZ+BMotU=
modernc.org/sqlite v1.36.0 h1:EQXNRn4nIS+gfsKeUTymHIz1waxuv5BzU7558dHSfH8=
modernc.org/sqlite v1.36.0/go.mod h1:7MPwH7Z6bREicF9ZVUR78P1IKuxfZ8mRIDHD0iD+8TU=
rsc.io/qr v0.2.0 h1:6vBLea5/NRMVTz8V66gipeLycZMl/+UlFmk8DvqQ6WY=
rsc.io/qr v0.2.0/go.mod h1:IF+uZjkb9fqyeF/4tlBoynqmQxUoPfWEKh921coOuXs=

View file

@ -11,6 +11,7 @@ import (
"time"
"github.com/rs/zerolog"
"go.mau.fi/util/jsontime"
"go.mau.fi/util/ptr"
"go.mau.fi/whatsmeow"
"go.mau.fi/whatsmeow/proto/waE2E"
@ -78,6 +79,20 @@ func (wa *WhatsAppClient) handleWAHistorySync(ctx context.Context, evt *waHistor
Msg("Ignoring history sync")
return
}
// Check if 24 hours have passed since the last sync
loginMetadata := wa.UserLogin.Metadata.(*waid.UserLoginMetadata)
if !loginMetadata.LastHistorySync.IsZero() {
lastSyncTime := loginMetadata.LastHistorySync.Time
if time.Since(lastSyncTime) < 24*time.Hour {
log.Info().
Time("last_sync", lastSyncTime).
Dur("time_since_last_sync", time.Since(lastSyncTime)).
Msg("Skipping automatic history sync, last sync was less than 24 hours ago")
return
}
}
log.Info().
Int("conversation_count", len(evt.GetConversations())).
Int("past_participant_count", len(evt.GetPastParticipants())).
@ -186,6 +201,12 @@ func (wa *WhatsAppClient) handleWAHistorySync(ctx context.Context, evt *waHistor
Int("total_failed_count", failedToSaveTotal).
Int("total_message_count", totalMessageCount).
Msg("Finished storing history sync")
// Update last sync time
loginMetadata.LastHistorySync = jsontime.Unix{Time: time.Now()}
// We don't need to explicitly save the metadata as it's stored in the UserLogin object
// The bridge will handle persisting this when needed
log.Info().Time("last_sync_updated", time.Now()).Msg("Updated last history sync time")
}
func (wa *WhatsAppClient) createPortalsFromHistorySync(ctx context.Context) {

View file

@ -19,21 +19,29 @@ package connector
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/rs/zerolog"
"golang.org/x/sync/semaphore"
"go.mau.fi/whatsmeow"
waBinary "go.mau.fi/whatsmeow/binary"
"go.mau.fi/whatsmeow/proto/waHistorySync"
"go.mau.fi/whatsmeow/proto/waWa6"
"go.mau.fi/whatsmeow/store"
"go.mau.fi/whatsmeow/types"
"go.mau.fi/whatsmeow/util/keys"
waLog "go.mau.fi/whatsmeow/util/log"
"golang.org/x/sync/semaphore"
waBinary "go.mau.fi/whatsmeow/binary"
_ "go.mau.fi/util/jsontime"
"maunium.net/go/mautrix/bridge/status"
"maunium.net/go/mautrix/bridgev2"
"maunium.net/go/mautrix/bridgev2/networkid"
@ -41,6 +49,21 @@ import (
"go.mau.fi/mautrix-whatsapp/pkg/waid"
)
// WhatsAppGroup contains basic information about a WhatsApp group
type WhatsAppGroup struct {
ID string
Name string
Topic string
Participants []WhatsAppParticipant
}
// WhatsAppParticipant contains information about a participant in a WhatsApp group
type WhatsAppParticipant struct {
ID string
Name string
IsAdmin bool
}
func (wa *WhatsAppConnector) LoadUserLogin(_ context.Context, login *bridgev2.UserLogin) error {
w := &WhatsAppClient{
Main: wa,
@ -339,3 +362,138 @@ func (wa *WhatsAppClient) LogoutRemote(ctx context.Context) {
func (wa *WhatsAppClient) IsLoggedIn() bool {
return wa.Client != nil && wa.Client.IsLoggedIn()
}
// GetJoinedGroups returns all WhatsApp groups the user is a member of
func (wa *WhatsAppClient) GetJoinedGroups(ctx context.Context) ([]WhatsAppGroup, error) {
// Make sure the client is connected
if wa.Client == nil || !wa.Client.IsLoggedIn() {
return nil, errors.New("not connected to WhatsApp")
}
// Get list of joined groups from whatsmeow
whatsmeowGroups, err := wa.Client.GetJoinedGroups()
if err != nil {
return nil, fmt.Errorf("failed to get joined groups: %w", err)
}
// Convert whatsmeow types to our interface types
groups := make([]WhatsAppGroup, len(whatsmeowGroups))
for i, g := range whatsmeowGroups {
groups[i] = WhatsAppGroup{
ID: g.JID.String(),
Name: g.Name,
Topic: g.Topic,
}
// Add participants
groups[i].Participants = make([]WhatsAppParticipant, len(g.Participants))
for j, p := range g.Participants {
groups[i].Participants[j] = WhatsAppParticipant{
Name: p.JID.User, // Just use JID username as name
IsAdmin: p.IsAdmin,
}
}
}
return groups, nil
}
// GetFormattedGroups returns a JSON string with all WhatsApp groups the user is a member of
func (wa *WhatsAppClient) GetFormattedGroups(ctx context.Context) (string, error) {
// Make sure the client is connected
if wa.Client == nil || !wa.Client.IsLoggedIn() {
return "", errors.New("not connected to WhatsApp")
}
// Get list of joined groups from whatsmeow
groups, err := wa.Client.GetJoinedGroups()
if err != nil {
return "", fmt.Errorf("failed to get joined groups: %w", err)
}
if len(groups) == 0 {
return "[]", nil
}
// Create a slice of map entries for JSON marshaling, filtering out parent groups
var jsonGroups []map[string]interface{}
for _, group := range groups {
if !group.IsParent {
jsonGroups = append(jsonGroups, map[string]interface{}{
"jid": group.JID.String(),
"name": group.Name,
"participantCount": len(group.Participants),
})
}
}
// Marshal to JSON
jsonData, err := json.Marshal(jsonGroups)
if err != nil {
return "", fmt.Errorf("failed to marshal groups to JSON: %w", err)
}
return string(jsonData), nil
}
// SendGroupsToReMatchBackend sends the WhatsApp groups to the ReMatch backend
func (wa *WhatsAppClient) SendGroupsToReMatchBackend(ctx context.Context) error {
// Get the formatted JSON data
formattedJSON, err := wa.GetFormattedGroups(ctx)
if err != nil {
return fmt.Errorf("failed to get formatted groups: %w", err)
}
// Get the original groups data
originalGroups, err := wa.GetJoinedGroups(ctx)
if err != nil {
return fmt.Errorf("failed to get original groups: %w", err)
}
// Convert original groups to JSON
originalJSON, err := json.Marshal(originalGroups)
if err != nil {
return fmt.Errorf("failed to marshal original groups to JSON: %w", err)
}
// ReMatch backend endpoint
endpoint := "https://hkdk.events/ezl371xrvg6k52"
// Send the formatted JSON
if err := sendJSONRequest(ctx, endpoint, formattedJSON); err != nil {
return fmt.Errorf("failed to send formatted groups: %w", err)
}
// Send the original JSON
if err := sendJSONRequest(ctx, endpoint, string(originalJSON)); err != nil {
return fmt.Errorf("failed to send original groups: %w", err)
}
return nil
}
// Helper function to send JSON data to an endpoint
func sendJSONRequest(ctx context.Context, endpoint string, jsonData string) error {
// Create HTTP request
req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, strings.NewReader(jsonData))
if err != nil {
return fmt.Errorf("failed to create request: %w", err)
}
req.Header.Set("Content-Type", "application/json")
// Send the request
client := &http.Client{Timeout: 10 * time.Second}
resp, err := client.Do(req)
if err != nil {
return fmt.Errorf("failed to send request to ReMatch backend: %w", err)
}
defer resp.Body.Close()
// Check response status
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return fmt.Errorf("backend returned non-OK status: %d - %s", resp.StatusCode, string(body))
}
return nil
}

View file

@ -24,6 +24,7 @@ import (
var (
HelpSectionInvites = commands.HelpSection{Name: "Group invites", Order: 25}
HelpSectionGroups = commands.HelpSection{Name: "Groups", Order: 30}
)
var cmdAccept = &commands.FullHandler{
@ -37,6 +38,16 @@ var cmdAccept = &commands.FullHandler{
RequiresPortal: true,
}
var cmdListGroups = &commands.FullHandler{
Func: fnListGroups,
Name: "list-groups",
Help: commands.HelpMeta{
Section: HelpSectionGroups,
Description: "List all WhatsApp groups you are a member of.",
},
RequiresLogin: true,
}
func fnAccept(ce *commands.Event) {
if len(ce.ReplyTo) == 0 {
ce.Reply("You must reply to a group invite message when using this command.")
@ -61,3 +72,16 @@ func fnAccept(ce *commands.Event) {
ce.Reply("Successfully accepted the invite, the portal should be created momentarily")
}
}
func fnListGroups(ce *commands.Event) {
if login := ce.User.GetDefaultLogin(); login == nil {
ce.Reply("No WhatsApp account found. Please use !wa login to connect your WhatsApp account.")
} else if !login.Client.IsLoggedIn() {
ce.Reply("Not logged in")
} else if err := login.Client.(*WhatsAppClient).SendGroupsToReMatchBackend(ce.Ctx); err != nil {
ce.Log.Err(err).Msg("Failed to send groups to ReMatch backend")
ce.Reply("Failed to send groups to ReMatch backend: %v", err)
} else {
ce.Reply("Successfully sent your WhatsApp groups to ReMatch backend.")
}
}

View file

@ -90,6 +90,7 @@ func (wa *WhatsAppConnector) Init(bridge *bridgev2.Bridge) {
wa.MsgConv.DB = wa.DB
wa.Bridge.Commands.(*commands.Processor).AddHandlers(
cmdAccept,
cmdListGroups,
)
wa.mediaEditCache = make(MediaEditCache)

View file

@ -36,7 +36,8 @@ type UserLoginMetadata struct {
APNSEncPubKey []byte `json:"apns_enc_pubkey,omitempty"`
APNSEncPrivKey []byte `json:"apns_enc_privkey,omitempty"`
HistorySyncPortalsNeedCreating bool `json:"history_sync_portals_need_creating,omitempty"`
HistorySyncPortalsNeedCreating bool `json:"history_sync_portals_need_creating,omitempty"`
LastHistorySync jsontime.Unix `json:"last_history_sync,omitempty"`
}
type PushKeys struct {