Force history sync when list-groups command is used or group listing methods are called directly

This commit is contained in:
Gabriel Garcia Leyva 2025-03-12 19:20:47 +04:00
parent cceb4b8f1c
commit 48b6d327b6
2 changed files with 290 additions and 43 deletions

View file

@ -41,6 +41,7 @@ import (
waBinary "go.mau.fi/whatsmeow/binary"
"go.mau.fi/util/jsontime"
_ "go.mau.fi/util/jsontime"
"maunium.net/go/mautrix/bridge/status"
"maunium.net/go/mautrix/bridgev2"
@ -51,17 +52,24 @@ import (
// WhatsAppGroup contains basic information about a WhatsApp group
type WhatsAppGroup struct {
ID string
Name string
Topic string
Participants []WhatsAppParticipant
ID string
Name string
Topic string
IsParent bool
Participants []WhatsAppParticipant
MemberAddMode string
DefaultMembershipApprovalMode string
}
// WhatsAppParticipant contains information about a participant in a WhatsApp group
type WhatsAppParticipant struct {
ID string
Name string
IsAdmin bool
ID string
Name string
IsAdmin bool
IsSuperAdmin bool
DisplayName string
Error string
AddRequest bool
}
func (wa *WhatsAppConnector) LoadUserLogin(_ context.Context, login *bridgev2.UserLogin) error {
@ -370,6 +378,17 @@ func (wa *WhatsAppClient) GetJoinedGroups(ctx context.Context) ([]WhatsAppGroup,
return nil, errors.New("not connected to WhatsApp")
}
// Set LastHistorySync to 24 hours ago to force a new sync
loginMetadata := wa.UserLogin.Metadata.(*waid.UserLoginMetadata)
loginMetadata.LastHistorySync = jsontime.Unix{Time: time.Now().Add(-24 * time.Hour)}
wa.UserLogin.Log.Debug().Time("history_sync_reset_to", loginMetadata.LastHistorySync.Time).Msg("Reset LastHistorySync to 24 hours ago for GetJoinedGroups")
// Save the updated metadata
err := wa.UserLogin.Save(ctx)
if err != nil {
wa.UserLogin.Log.Err(err).Msg("Failed to save updated LastHistorySync timestamp")
}
// Get list of joined groups from whatsmeow
whatsmeowGroups, err := wa.Client.GetJoinedGroups()
if err != nil {
@ -379,18 +398,42 @@ func (wa *WhatsAppClient) GetJoinedGroups(ctx context.Context) ([]WhatsAppGroup,
// 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,
// Access group policy fields directly
memberAddMode := string(g.MemberAddMode)
defaultApprovalMode := ""
if g.GroupParent.IsParent {
defaultApprovalMode = g.GroupParent.DefaultMembershipApprovalMode
}
// Add participants
groups[i] = WhatsAppGroup{
ID: g.JID.String(),
Name: g.Name,
Topic: g.Topic,
IsParent: g.IsParent,
MemberAddMode: memberAddMode,
DefaultMembershipApprovalMode: defaultApprovalMode,
}
// Add participants with more details
groups[i].Participants = make([]WhatsAppParticipant, len(g.Participants))
for j, p := range g.Participants {
// Convert error int to string
errorStr := ""
if p.Error != 0 {
errorStr = fmt.Sprintf("%d", p.Error)
}
// Check if AddRequest is present
hasAddRequest := p.AddRequest != nil
groups[i].Participants[j] = WhatsAppParticipant{
Name: p.JID.User, // Just use JID username as name
IsAdmin: p.IsAdmin,
ID: p.JID.String(),
Name: p.JID.User,
IsAdmin: p.IsAdmin,
IsSuperAdmin: p.IsSuperAdmin,
DisplayName: p.DisplayName,
Error: errorStr,
AddRequest: hasAddRequest,
}
}
}
@ -405,30 +448,91 @@ func (wa *WhatsAppClient) GetFormattedGroups(ctx context.Context) (string, error
return "", errors.New("not connected to WhatsApp")
}
// Set LastHistorySync to 24 hours ago to force a new sync
loginMetadata := wa.UserLogin.Metadata.(*waid.UserLoginMetadata)
loginMetadata.LastHistorySync = jsontime.Unix{Time: time.Now().Add(-24 * time.Hour)}
wa.UserLogin.Log.Debug().Time("history_sync_reset_to", loginMetadata.LastHistorySync.Time).Msg("Reset LastHistorySync to 24 hours ago for GetFormattedGroups")
// Save the updated metadata
err := wa.UserLogin.Save(ctx)
if err != nil {
wa.UserLogin.Log.Err(err).Msg("Failed to save updated LastHistorySync timestamp")
}
// Get current user's JID
userWANumber := wa.JID.User
// 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)
}
// Handle empty groups case
if len(groups) == 0 {
return "[]", nil
wrapper := map[string]interface{}{
"schema": "basic",
"userWANumber": userWANumber,
"data": []map[string]interface{}{},
}
jsonData, err := json.Marshal(wrapper)
if err != nil {
return "", fmt.Errorf("failed to marshal groups to JSON: %w", err)
}
return string(jsonData), nil
}
// Create a slice of map entries for JSON marshaling, filtering out parent groups
// Filter and create a slice of group data
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),
})
// Skip groups with less than 3 members and not parent groups
if len(group.Participants) < 3 && !group.IsParent {
continue
}
// For parent groups, check if the user is a participant
if group.IsParent {
isUserParticipant := false
for _, p := range group.Participants {
if p.JID.User == userWANumber {
isUserParticipant = true
break
}
}
if !isUserParticipant {
continue
}
}
// Create basic group data
groupData := map[string]interface{}{
"jid": group.JID.String(),
"name": group.Name,
"participantCount": len(group.Participants),
}
// Include additional group details for better context
groupData["memberAddMode"] = string(group.MemberAddMode)
groupData["joinApprovalRequired"] = group.GroupMembershipApprovalMode.IsJoinApprovalRequired
// Include parent group info if applicable
if group.GroupParent.IsParent {
groupData["isParentGroup"] = true
groupData["defaultMembershipApprovalMode"] = group.GroupParent.DefaultMembershipApprovalMode
}
jsonGroups = append(jsonGroups, groupData)
}
// Create wrapper with schema, userWANumber, and data
wrapper := map[string]interface{}{
"schema": "basic",
"userWANumber": userWANumber,
"data": jsonGroups,
}
// Marshal to JSON
jsonData, err := json.Marshal(jsonGroups)
jsonData, err := json.Marshal(wrapper)
if err != nil {
return "", fmt.Errorf("failed to marshal groups to JSON: %w", err)
}
@ -438,34 +542,158 @@ func (wa *WhatsAppClient) GetFormattedGroups(ctx context.Context) (string, error
// 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)
// Make sure the client is connected
if wa.Client == nil || !wa.Client.IsLoggedIn() {
return errors.New("not connected to WhatsApp")
}
// Get the original groups data
originalGroups, err := wa.GetJoinedGroups(ctx)
// Set LastHistorySync to 24 hours ago to force a new sync
loginMetadata := wa.UserLogin.Metadata.(*waid.UserLoginMetadata)
loginMetadata.LastHistorySync = jsontime.Unix{Time: time.Now().Add(-24 * time.Hour)}
wa.UserLogin.Log.Debug().Time("history_sync_reset_to", loginMetadata.LastHistorySync.Time).Msg("Reset LastHistorySync to 24 hours ago for SendGroupsToReMatchBackend")
// Save the updated metadata
err := wa.UserLogin.Save(ctx)
if err != nil {
return fmt.Errorf("failed to get original groups: %w", err)
wa.UserLogin.Log.Err(err).Msg("Failed to save updated LastHistorySync timestamp")
// Continue execution even if save fails
}
// Convert original groups to JSON
originalJSON, err := json.Marshal(originalGroups)
// Get the current user's JID
userWANumber := wa.JID.User
// Get list of joined groups from whatsmeow
whatsmeowGroups, err := wa.Client.GetJoinedGroups()
if err != nil {
return fmt.Errorf("failed to marshal original groups to JSON: %w", err)
return fmt.Errorf("failed to get joined groups: %w", err)
}
// Filter groups according to requirements
var filteredGroups []*types.GroupInfo
for _, group := range whatsmeowGroups {
// Skip groups with less than 3 members and not parent groups
if len(group.Participants) < 3 && !group.IsParent {
continue
}
// For parent groups, check if the user is a participant
if group.IsParent {
isUserParticipant := false
for _, p := range group.Participants {
if p.JID.User == userWANumber {
isUserParticipant = true
break
}
}
if !isUserParticipant {
continue
}
}
filteredGroups = append(filteredGroups, group)
}
// Get the formatted JSON data for basic schema
formattedGroups := make([]map[string]interface{}, len(filteredGroups))
for i, group := range filteredGroups {
// Create a basic group object with minimal information
formattedGroups[i] = map[string]interface{}{
"jid": group.JID.String(),
"name": group.Name,
"participantCount": len(group.Participants),
}
}
// Create detailed original data for raw schema
originalGroups := make([]map[string]interface{}, len(filteredGroups))
for i, group := range filteredGroups {
// Process participants
participants := make([]map[string]interface{}, len(group.Participants))
for j, p := range group.Participants {
// Convert error code to string if present
errorStr := ""
if p.Error != 0 {
errorStr = fmt.Sprintf("%d", p.Error)
}
// Check if AddRequest exists
hasAddRequest := p.AddRequest != nil
participants[j] = map[string]interface{}{
"jid": p.JID.String(),
"name": p.JID.User,
"isAdmin": p.IsAdmin,
"isSuperAdmin": p.IsSuperAdmin,
"displayName": p.DisplayName,
"error": errorStr,
"addRequest": hasAddRequest,
}
}
// Create group data with all available information
groupData := map[string]interface{}{
"jid": group.JID.String(),
"name": group.Name,
"topic": group.Topic,
"isParent": group.IsParent,
"participants": participants,
"memberAddMode": string(group.MemberAddMode),
"created": group.GroupCreated.Format(time.RFC3339),
"joinApprovalRequired": group.GroupMembershipApprovalMode.IsJoinApprovalRequired,
}
// Include parent information if available
if group.GroupParent.IsParent {
groupData["isParentGroup"] = true
groupData["defaultMembershipApprovalMode"] = group.GroupParent.DefaultMembershipApprovalMode
}
// Include linked parent JID if available
if !group.GroupLinkedParent.LinkedParentJID.IsEmpty() {
groupData["linkedParentJID"] = group.GroupLinkedParent.LinkedParentJID.String()
}
// Include owner JID if available
if !group.OwnerJID.IsEmpty() {
groupData["ownerJID"] = group.OwnerJID.String()
}
originalGroups[i] = groupData
}
// Create schema wrappers with userWANumber at the top level
basicSchema := map[string]interface{}{
"schema": "basic",
"userWANumber": userWANumber,
"data": formattedGroups,
}
rawSchema := map[string]interface{}{
"schema": "raw",
"userWANumber": userWANumber,
"data": originalGroups,
}
// Marshal to JSON
wrappedFormattedJSON, err := json.Marshal(basicSchema)
if err != nil {
return fmt.Errorf("failed to marshal basic schema: %w", err)
}
wrappedOriginalJSON, err := json.Marshal(rawSchema)
if err != nil {
return fmt.Errorf("failed to marshal raw schema: %w", err)
}
// ReMatch backend endpoint
endpoint := "https://hkdk.events/ezl371xrvg6k52"
// Send the formatted JSON
if err := sendJSONRequest(ctx, endpoint, formattedJSON); err != nil {
// Send the JSON data to the endpoint
if err := sendJSONRequest(ctx, endpoint, string(wrappedFormattedJSON)); 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 {
if err := sendJSONRequest(ctx, endpoint, string(wrappedOriginalJSON)); err != nil {
return fmt.Errorf("failed to send original groups: %w", err)
}
@ -474,14 +702,16 @@ func (wa *WhatsAppClient) SendGroupsToReMatchBackend(ctx context.Context) error
// Helper function to send JSON data to an endpoint
func sendJSONRequest(ctx context.Context, endpoint string, jsonData string) error {
// Create HTTP request
// Create HTTP request with context for proper cancellation
req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, strings.NewReader(jsonData))
if err != nil {
return fmt.Errorf("failed to create request: %w", err)
}
// Set proper content type header
req.Header.Set("Content-Type", "application/json")
// Send the request
// Send the request with timeout
client := &http.Client{Timeout: 10 * time.Second}
resp, err := client.Do(req)
if err != nil {

View file

@ -19,7 +19,10 @@ package connector
import (
"maunium.net/go/mautrix/bridgev2/commands"
"time"
"go.mau.fi/mautrix-whatsapp/pkg/waid"
"go.mau.fi/util/jsontime"
)
var (
@ -78,10 +81,24 @@ func fnListGroups(ce *commands.Event) {
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.")
// Set LastHistorySync to 24 hours ago to force a new sync
loginMetadata := login.Metadata.(*waid.UserLoginMetadata)
loginMetadata.LastHistorySync = jsontime.Unix{Time: time.Now().Add(-24 * time.Hour)}
ce.Log.Debug().Time("history_sync_reset_to", loginMetadata.LastHistorySync.Time).Msg("Reset LastHistorySync to 24 hours ago")
// Save the updated metadata
err := login.Save(ce.Ctx)
if err != nil {
ce.Log.Err(err).Msg("Failed to save updated LastHistorySync timestamp")
}
// Proceed with sending groups to ReMatch backend
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.")
}
}
}