Skip to content

Add on-demand history sync request - #215

Open
osyris wants to merge 3 commits into
krypton-byte:masterfrom
ai-cluster-one:history-sync-on-demand
Open

Add on-demand history sync request#215
osyris wants to merge 3 commits into
krypton-byte:masterfrom
ai-cluster-one:history-sync-on-demand

Conversation

@osyris

@osyris osyris commented Sep 8, 2026

Copy link
Copy Markdown

The gap

A linked device has no server-side archive to read from: WhatsApp hands over a
shallow slice of history once at pairing time and never resends it. Anything
older lives on the phone.

whatsmeow covers this with BuildHistorySyncRequest + SendPeerMessage, but
neither is bound in neonize, so from Python there is currently no way to reach
messages older than whatever arrived at pairing. In practice that means a few
messages per chat.

The change

Exposes the pair as one call.

  • goneonize: RequestHistorySync export, following the surrounding pattern
    (ProtoReturnV3, clients, utils.DecodeJidProto, SendMessageReturnFunction)
  • _binder.py: ctypes signature
  • client.py: NewClient.request_history_sync(chat, message_id, from_me, timestamp, count=50)

The request is anchored on a message the caller already holds; the phone answers
with up to count messages immediately preceding it. As documented upstream, the
answer is asynchronous — it arrives as a HistorySyncEv whose
Data.syncType is ON_DEMAND, not as this call's return value. Paging further
back is re-anchoring on the oldest message held and calling again.

@client.event(HistorySyncEv)
def on_history(_, ev):
    if ev.Data.syncType == 6:  # ON_DEMAND
        ...

client.request_history_sync(chat_jid, oldest_id, oldest_from_me, oldest_ts, 50)

Two caveats worth knowing, both inherent to the mechanism rather than to this
binding: the phone must be online to answer, and count=50 is what whatsmeow
recommends per request.

Testing

Verified against a live linked account.

  • A single request returns exactly count messages, roughly 4s round trip.
  • Paging back repeatedly works: 20 consecutive rounds of 50 all answered in
    full, taking one group chat from 728 to 2327 messages and its oldest message
    from 2026-06-10 back to 2025-11-29.
  • Continuing to the end of available history, the answers degrade the way you
    would expect at a real boundary: a short chunk (42 of 50) followed by an
    immediate empty answer. Final state for that chat was 3619 messages spanning a
    full year, up from 728 at pairing.
  • The public Python method was exercised end to end on top of this branch's own
    build: request accepted, syncType=6, 50 messages returned.

No existing exports or signatures are touched; the change is additive.

KZ and others added 3 commits September 8, 2026 14:33
WhatsApp keeps no server-side archive for a linked device, so messages
older than what a client already holds can only be obtained by asking the
primary device for them. whatsmeow supports this through
BuildHistorySyncRequest + SendPeerMessage, but neither was bound, leaving
no way to reach history from Python.

Expose the pair as a single call. The request is anchored on a message the
client already has; the phone answers with up to `count` messages
immediately preceding it, delivered asynchronously as a HistorySync event
with syncType ON_DEMAND. Paging further back is a matter of re-anchoring on
the oldest message held and calling again.

- goneonize: RequestHistorySync export, following the existing pattern
- _binder: ctypes signature
- client: NewClient.request_history_sync()

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Peer messages carry the whole PeerDataOperationRequest family — on-demand
history, full history sync over a time window, placeholder resends for
messages that failed to decrypt, chunk retries. whatsmeow ships a builder
for only one of them, so binding each operation separately would mean a new
export every time a caller needs another.

Take a constructed waE2E.Message instead and let the caller choose the
operation.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Every message addressed to a person carried a <bot biz_bot="1"/> node, and
the recipient's WhatsApp renders it as an "AI" badge. It is also a claim
about the account rather than about the message: traffic marked automated
is judged as automated.

A library cannot know whether its caller wants that claim made. The caller
that does can say so; the caller that does not should not have to discover
the node exists to be rid of it. So NEONIZE_BOT_TAG turns it on instead of
off, and the default is silence.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant