Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion appstate.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ func (cli *Client) filterContacts(mutations []appstate.Mutation) ([]appstate.Mut
func (cli *Client) dispatchAppState(mutation appstate.Mutation, fullSync bool, emitOnFullSync bool) {
dispatchEvts := !fullSync || emitOnFullSync

if mutation.Operation != waServerSync.SyncdMutation_SET {
if (mutation.Action != nil && mutation.Action.ContactAction == nil) && mutation.Operation != waServerSync.SyncdMutation_SET {
return
}

Expand Down
90 changes: 50 additions & 40 deletions appstate/encode.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ type PatchInfo struct {
Timestamp time.Time
// Type is the app state type being mutated.
Type WAPatchName
// Operation is SET / REMOVE
Operation waServerSync.SyncdMutation_SyncdOperation
// Mutations contains the individual mutations to apply to the app state in this patch.
Mutations []MutationInfo
}
Expand All @@ -46,7 +48,8 @@ func BuildMute(target types.JID, mute bool, muteDuration time.Duration) PatchInf
}

return PatchInfo{
Type: WAPatchRegularHigh,
Type: WAPatchRegularHigh,
Operation: waServerSync.SyncdMutation_SET,
Mutations: []MutationInfo{{
Index: []string{IndexMute, target.String()},
Version: 2,
Expand All @@ -60,6 +63,37 @@ func BuildMute(target types.JID, mute bool, muteDuration time.Duration) PatchInf
}
}

func BuildAddContact(target types.JID, fullName string) PatchInfo {
return PatchInfo{
Type: WAPatchCriticalUnblockLow,
Operation: waServerSync.SyncdMutation_SET,
Mutations: []MutationInfo{{
Index: []string{IndexContact, target.String()},
Version: 2,
Value: &waSyncAction.SyncActionValue{
ContactAction: &waSyncAction.ContactAction{
FullName: &fullName,
SaveOnPrimaryAddressbook: proto.Bool(true),
},
},
}},
}
}

func BuildRemoveContact(target types.JID) PatchInfo {
return PatchInfo{
Type: WAPatchCriticalUnblockLow,
Operation: waServerSync.SyncdMutation_REMOVE,
Mutations: []MutationInfo{{
Index: []string{IndexContact, target.String()},
Version: 2,
Value: &waSyncAction.SyncActionValue{
ContactAction: &waSyncAction.ContactAction{},
},
}},
}
}

func newPinMutationInfo(target types.JID, pin bool) MutationInfo {
return MutationInfo{
Index: []string{IndexPin, target.String()},
Expand All @@ -75,7 +109,8 @@ func newPinMutationInfo(target types.JID, pin bool) MutationInfo {
// BuildPin builds an app state patch for pinning or unpinning a chat.
func BuildPin(target types.JID, pin bool) PatchInfo {
return PatchInfo{
Type: WAPatchRegularLow,
Type: WAPatchRegularLow,
Operation: waServerSync.SyncdMutation_SET,
Mutations: []MutationInfo{
newPinMutationInfo(target, pin),
},
Expand Down Expand Up @@ -119,6 +154,7 @@ func BuildArchive(target types.JID, archive bool, lastMessageTimestamp time.Time

result := PatchInfo{
Type: WAPatchRegularLow,
Operation: waServerSync.SyncdMutation_SET,
Mutations: mutations,
}

Expand All @@ -140,7 +176,8 @@ func newLabelChatMutation(target types.JID, labelID string, labeled bool) Mutati
// BuildLabelChat builds an app state patch for labeling or un(labeling) a chat.
func BuildLabelChat(target types.JID, labelID string, labeled bool) PatchInfo {
return PatchInfo{
Type: WAPatchRegular,
Type: WAPatchRegular,
Operation: waServerSync.SyncdMutation_SET,
Mutations: []MutationInfo{
newLabelChatMutation(target, labelID, labeled),
},
Expand All @@ -162,7 +199,8 @@ func newLabelMessageMutation(target types.JID, labelID, messageID string, labele
// BuildLabelMessage builds an app state patch for labeling or un(labeling) a message.
func BuildLabelMessage(target types.JID, labelID, messageID string, labeled bool) PatchInfo {
return PatchInfo{
Type: WAPatchRegular,
Type: WAPatchRegular,
Operation: waServerSync.SyncdMutation_SET,
Mutations: []MutationInfo{
newLabelMessageMutation(target, labelID, messageID, labeled),
},
Expand All @@ -186,7 +224,8 @@ func newLabelEditMutation(labelID string, labelName string, labelColor int32, de
// BuildLabelEdit builds an app state patch for editing a label.
func BuildLabelEdit(labelID string, labelName string, labelColor int32, deleted bool) PatchInfo {
return PatchInfo{
Type: WAPatchRegular,
Type: WAPatchRegular,
Operation: waServerSync.SyncdMutation_SET,
Mutations: []MutationInfo{
newLabelEditMutation(labelID, labelName, labelColor, deleted),
},
Expand All @@ -208,7 +247,8 @@ func newSettingPushNameMutation(pushName string) MutationInfo {
// BuildSettingPushName builds an app state patch for setting the push name.
func BuildSettingPushName(pushName string) PatchInfo {
return PatchInfo{
Type: WAPatchCriticalBlock,
Type: WAPatchCriticalBlock,
Operation: waServerSync.SyncdMutation_SET,
Mutations: []MutationInfo{
newSettingPushNameMutation(pushName),
},
Expand Down Expand Up @@ -238,43 +278,13 @@ func BuildStar(target, sender types.JID, messageID types.MessageID, fromMe, star
senderJID = "0"
}
return PatchInfo{
Type: WAPatchRegularHigh,
Type: WAPatchRegularHigh,
Operation: waServerSync.SyncdMutation_SET,
Mutations: []MutationInfo{
newStarMutation(targetJID, senderJID, messageID, isFromMe, starred),
},
}
}
// https://github.com/tulir/whatsmeow/pull/702/
func newDeleteForMeMutation(targetJID, senderJID string, messageInfo types.MessageInfo, fromMe string, deleteMedia bool) MutationInfo {
return MutationInfo{
Index: []string{IndexDeleteMessageForMe, targetJID, messageInfo.ID, fromMe, senderJID},
Version: 2,
Value: &waSyncAction.SyncActionValue{
DeleteMessageForMeAction: &waSyncAction.DeleteMessageForMeAction{
DeleteMedia: proto.Bool(deleteMedia),
MessageTimestamp: proto.Int64(messageInfo.Timestamp.UnixMilli()),
},
},
}
}

// BuildDeleteForMe builds an app state patch for deleting a message for me.
func BuildDeleteForMe(messageInfo types.MessageInfo, deleteMedia bool) PatchInfo {
isFromMe := "0"
if messageInfo.IsFromMe {
isFromMe = "1"
}
targetJID, senderJID := messageInfo.Chat.String(), messageInfo.Sender.String()
if messageInfo.Chat.User == messageInfo.Sender.User {
senderJID = "0"
}
return PatchInfo{
Type: WAPatchRegularHigh,
Mutations: []MutationInfo{
newDeleteForMeMutation(targetJID, senderJID, messageInfo, isFromMe, deleteMedia),
},
}
}

func (proc *Processor) EncodePatch(keyID []byte, state HashState, patchInfo PatchInfo) ([]byte, error) {
keys, err := proc.getAppStateKey(keyID)
Expand Down Expand Up @@ -312,11 +322,11 @@ func (proc *Processor) EncodePatch(keyID []byte, state HashState, patchInfo Patc
return nil, fmt.Errorf("failed to encrypt mutation: %w", err)
}

valueMac := generateContentMAC(waServerSync.SyncdMutation_SET, encryptedContent, keyID, keys.ValueMAC)
valueMac := generateContentMAC(patchInfo.Operation, encryptedContent, keyID, keys.ValueMAC)
indexMac := concatAndHMAC(sha256.New, keys.Index, indexBytes)

mutations = append(mutations, &waServerSync.SyncdMutation{
Operation: waServerSync.SyncdMutation_SET.Enum(),
Operation: patchInfo.Operation.Enum(),
Record: &waServerSync.SyncdRecord{
Index: &waServerSync.SyncdIndex{Blob: indexMac},
Value: &waServerSync.SyncdValue{Blob: append(encryptedContent, valueMac...)},
Expand Down
52 changes: 41 additions & 11 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,10 +120,10 @@ type Client struct {

privacySettingsCache atomic.Value

groupParticipantsCache map[types.JID][]types.JID
groupParticipantsCacheLock sync.Mutex
userDevicesCache map[types.JID]deviceCache
userDevicesCacheLock sync.Mutex
groupCache map[types.JID]*groupMetaCache
groupCacheLock sync.Mutex
userDevicesCache map[types.JID]deviceCache
userDevicesCacheLock sync.Mutex

recentMessagesMap map[recentMessageKey]RecentMessage
recentMessagesList [recentMessagesSize]recentMessageKey
Expand Down Expand Up @@ -174,6 +174,12 @@ type Client struct {
RefreshCAT func() error
}

type groupMetaCache struct {
AddressingMode types.AddressingMode
CommunityAnnouncementGroup bool
Members []types.JID
}

type MessengerConfig struct {
UserAgent string
BaseURL string
Expand Down Expand Up @@ -226,8 +232,8 @@ func NewClient(deviceStore *store.Device, log waLog.Logger) *Client {

historySyncNotifications: make(chan *waE2E.HistorySyncNotification, 32),

groupParticipantsCache: make(map[types.JID][]types.JID),
userDevicesCache: make(map[types.JID]deviceCache),
groupCache: make(map[types.JID]*groupMetaCache),
userDevicesCache: make(map[types.JID]deviceCache),

recentMessagesMap: make(map[recentMessageKey]RecentMessage, recentMessagesSize),
sessionRecreateHistory: make(map[types.JID]time.Time),
Expand Down Expand Up @@ -378,11 +384,14 @@ func (cli *Client) getOwnID() types.JID {
if cli == nil {
return types.EmptyJID
}
id := cli.Store.ID
if id == nil {
return cli.Store.GetJID()
}

func (cli *Client) getOwnLID() types.JID {
if cli == nil {
return types.EmptyJID
}
return *id
return cli.Store.GetLID()
}

func (cli *Client) WaitForConnection(timeout time.Duration) bool {
Expand Down Expand Up @@ -816,7 +825,7 @@ func (cli *Client) ParseWebMessage(chatJID types.JID, webMsg *waWeb.WebMessageIn
IsFromMe: webMsg.GetKey().GetFromMe(),
IsGroup: chatJID.Server == types.GroupServer,
},
ID: webMsg.GetKey().GetId(),
ID: webMsg.GetKey().GetID(),
PushName: webMsg.GetPushName(),
Timestamp: time.Unix(int64(webMsg.GetMessageTimestamp()), 0),
}
Expand All @@ -825,7 +834,7 @@ func (cli *Client) ParseWebMessage(chatJID types.JID, webMsg *waWeb.WebMessageIn
if info.Sender.IsEmpty() {
return nil, ErrNotLoggedIn
}
} else if chatJID.Server == types.DefaultUserServer || chatJID.Server == types.NewsletterServer {
} else if chatJID.Server == types.DefaultUserServer || chatJID.Server == types.HiddenUserServer || chatJID.Server == types.NewsletterServer {
info.Sender = chatJID
} else if webMsg.GetParticipant() != "" {
info.Sender, err = types.ParseJID(webMsg.GetParticipant())
Expand All @@ -837,6 +846,10 @@ func (cli *Client) ParseWebMessage(chatJID types.JID, webMsg *waWeb.WebMessageIn
if err != nil {
return nil, fmt.Errorf("failed to parse sender of message %s: %v", info.ID, err)
}
if pk := webMsg.GetCommentMetadata().GetCommentParentKey(); pk != nil {
info.MsgMetaInfo.ThreadMessageID = pk.GetID()
info.MsgMetaInfo.ThreadMessageSenderJID, _ = types.ParseJID(pk.GetParticipant())
}
evt := &events.Message{
RawMessage: webMsg.GetMessage(),
SourceWebMsg: webMsg,
Expand All @@ -849,3 +862,20 @@ func (cli *Client) ParseWebMessage(chatJID types.JID, webMsg *waWeb.WebMessageIn
}
return evt, nil
}

func (cli *Client) StoreLIDPNMapping(ctx context.Context, first, second types.JID) {
var lid, pn types.JID
if first.Server == types.HiddenUserServer && second.Server == types.DefaultUserServer {
lid = first
pn = second
} else if first.Server == types.DefaultUserServer && second.Server == types.HiddenUserServer {
lid = second
pn = first
} else {
return
}
err := cli.Store.LIDs.PutLIDMapping(ctx, lid, pn)
if err != nil {
cli.Log.Errorf("Failed to store LID-PN mapping for %s -> %s: %v", lid, pn, err)
}
}
9 changes: 9 additions & 0 deletions connectionevents.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,15 @@ func (cli *Client) handleConnectSuccess(node *waBinary.Node) {
cli.LastSuccessfulConnect = time.Now()
cli.AutoReconnectErrors = 0
cli.isLoggedIn.Store(true)
if cli.Store.LID.IsEmpty() {
cli.Store.LID = node.AttrGetter().JID("lid")
err := cli.Store.Save()
if err != nil {
cli.Log.Warnf("Failed to save device after updating LID: %v", err)
} else {
cli.Log.Infof("Updated LID to %s", cli.Store.LID)
}
}
go func() {
if dbCount, err := cli.Store.PreKeys.UploadedPreKeyCount(); err != nil {
cli.Log.Errorf("Failed to get number of prekeys in database: %v", err)
Expand Down
1 change: 1 addition & 0 deletions errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ var (
var (
ErrOriginalMessageSecretNotFound = errors.New("original message secret key not found")
ErrNotEncryptedReactionMessage = errors.New("given message isn't an encrypted reaction message")
ErrNotEncryptedCommentMessage = errors.New("given message isn't an encrypted comment message")
ErrNotPollUpdateMessage = errors.New("given message isn't a poll update message")
)

Expand Down
12 changes: 7 additions & 5 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,24 @@ module github.com/techwiz37/waSocket

go 1.23.0

toolchain go1.24.0
toolchain go1.24.1

require (
github.com/google/uuid v1.6.0
github.com/gorilla/websocket v1.5.0
github.com/rs/zerolog v1.33.0
go.mau.fi/libsignal v0.1.2
go.mau.fi/util v0.8.5
golang.org/x/crypto v0.33.0
golang.org/x/net v0.35.0
go.mau.fi/util v0.8.6
golang.org/x/crypto v0.36.0
golang.org/x/net v0.37.0
google.golang.org/protobuf v1.36.5
)

require (
filippo.io/edwards25519 v1.1.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
golang.org/x/sys v0.30.0 // indirect
github.com/petermattis/goid v0.0.0-20250303134427-723919f7f203 // indirect
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect
golang.org/x/sys v0.31.0 // indirect
)
Loading
Loading