Skip to content

fix: group description parsing, ephemeral trigger, participating roundtrip, business hour types - #488

Merged
jlucaso1 merged 3 commits into
mainfrom
fix/group-description-parsing
Apr 6, 2026
Merged

fix: group description parsing, ephemeral trigger, participating roundtrip, business hour types#488
jlucaso1 merged 3 commits into
mainfrom
fix/group-description-parsing

Conversation

@jlucaso1

@jlucaso1 jlucaso1 commented Apr 6, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Group description always None: IQ metadata parser read text directly from <description> content, but WA Web wraps it in <description><body>text</body></description> (verified via WAWebInGroupsGroupInfoDescriptionMixin). Also added missing description_owner and description_time fields.
  • Ephemeral trigger not parsed: <ephemeral expiration="X" trigger="Y"> — we parsed expiration but missed trigger (optional int 0-20, per InGroupsGroupInfoMixin).
  • GroupParticipatingRequest roundtrip broken: try_from_node() always returned Self::default(), ignoring whether <participants> and <description> children were present.
  • Business hours type mismatch: open_time/close_time were Option<String> but WA Web parses them as integers with default 0 (ParsersParseBusinessProfile).

DRY improvements

  • Added Node::content_as_string() helper for the repeated String/Bytes extraction pattern
  • Derived Default on GroupMetadata, used ..Default::default() in tests

Test plan

  • test_group_info_response_parses_description_from_body — description, id, owner, time from <body> child
  • test_group_info_response_no_description — None when no description node
  • All existing tests pass (cargo test --workspace --exclude e2e-tests)
  • Clippy clean

Summary by CodeRabbit

Release Notes

  • New Features

    • Group descriptions now capture owner and timestamp information
    • Groups now support disappearing message trigger tracking
  • Improvements

    • Enhanced group information handling with improved description metadata support
    • Refined business hours time field processing for better configuration management
    • Improved internal content extraction and parsing capabilities

The IQ metadata parser was reading description text directly from the
<description> node content, but WA Web wraps it in a <body> child:
<description id="..." participant="..." t="..."><body>text</body></description>

This caused description to always be None when fetching group metadata
via IQ (sync_groups / get_metadata). The stanza notification parser
already handled this correctly.

Also adds description_owner and description_time fields that WA Web
extracts (WAWebInGroupsGroupInfoDescriptionMixin), adds
Node::content_as_string() helper to DRY the String/Bytes extraction
pattern, and derives Default on GroupMetadata.
@jlucaso1

jlucaso1 commented Apr 6, 2026

Copy link
Copy Markdown
Collaborator Author

@claude review

@claude

claude Bot commented Apr 6, 2026

Copy link
Copy Markdown

Claude finished @jlucaso1's task in 1m 6s —— View job


Code Review: fix group description parsing

  • Read changed files and understand the diff
  • Review for correctness, bugs, and style
  • Post review feedback

Looks good overall — clean fix for a real parsing bug, well-tested, and the content_as_string() helper is a nice DRY improvement. A few observations:

Correctness

  • The parsing change from reading <description> content directly to <description><body>text</body></description> correctly matches WhatsApp Web's structure.
  • The into_node() serialization was updated to build the matching nested <body> child — round-trip is consistent.
  • New fields (description_owner, description_time) are plumbed through both GroupInfoResponse and GroupMetadata.

Minor suggestions:

  1. into_node() condition broadening (wacore/src/iq/groups.rs:491): The condition changed from self.description.is_some() to self.description.is_some() || self.description_id.is_some(). This is correct — a <description> node can carry just an id attribute with no body (e.g., after a description deletion). Good catch.

  2. content_as_string on Node only (wacore/binary/src/node.rs:442): The helper is added on Node but not on NodeRef. This is fine for current usage, but if you find yourself needing it on NodeRef later, consider adding it there too. Not blocking.

  3. Default derive on GroupMetadata (src/features/groups.rs:21): This is a nice cleanup that simplifies the test. Worth noting that Default for Jid (the id field) produces an empty/zero JID — as long as callers only use Default in tests or From<GroupInfoResponse>, this is safe.

  4. No round-trip test for into_node()try_from_node(): The new test verifies parsing, but doesn't test that into_node() produces a node that parses back correctly with the new fields. Consider adding a round-trip test to ensure the serialization/deserialization stays in sync. Not blocking.

Style

  • Comments are concise and explain why (the WA Web mixin reference).
  • The content_as_string() helper eliminates duplicate match arms in stanza/groups.rs.
  • No unnecessary changes outside the fix scope.

LGTM — ship it 🚢

@github-actions

github-actions Bot commented Apr 6, 2026

Copy link
Copy Markdown

🐰 Bencher Report

Branchfix/group-description-parsing
Testbedubuntu-latest

🚨 1 Alert

BenchmarkMeasure
Units
ViewBenchmark Result
(Result Δ%)
Upper Boundary
(Limit %)
libsignal_benchmark::session_optimization_group::bench_decrypt_with_previous_session previous_session:setup_with_archived_sessions()Instructions
instructions x 1e3
📈 plot
🚷 threshold
🚨 alert (🔔)
47.13 x 1e3
(+8.22%)Baseline: 43.55 x 1e3
45.73 x 1e3
(103.07%)

Click to view all benchmark results
BenchmarkInstructionsBenchmark Result
instructions
(Result Δ%)
Upper Boundary
instructions
(Limit %)
binary_benchmark::attr_parser_group::bench_attr_parser attr_lookup:setup_attr_marshaled()📈 view plot
🚷 view threshold
6,197.00
(-3.56%)Baseline: 6,425.92
6,747.21
(91.85%)
binary_benchmark::child_iteration_group::bench_get_children_by_tag📈 view plot
🚷 view threshold
524,304.00
(-24.94%)Baseline: 698,533.74
733,460.42
(71.48%)
binary_benchmark::jid_optimization_group::bench_jid_to_owned_access jid_access:setup_jid_heavy_marshaled()📈 view plot
🚷 view threshold
20,868.00
(-5.10%)Baseline: 21,989.07
23,088.53
(90.38%)
binary_benchmark::marshal_group::bench_marshal_allocating📈 view plot
🚷 view threshold
98,202.00
(-12.57%)Baseline: 112,320.75
117,936.78
(83.27%)
binary_benchmark::marshal_group::bench_marshal_auto_allocating📈 view plot
🚷 view threshold
98,230.00
(-8.99%)Baseline: 107,931.04
113,327.59
(86.68%)
binary_benchmark::marshal_group::bench_marshal_auto_huge_bytes_allocating📈 view plot
🚷 view threshold
532,948.00
(-0.09%)Baseline: 533,453.39
560,126.06
(95.15%)
binary_benchmark::marshal_group::bench_marshal_auto_long_string📈 view plot
🚷 view threshold
15,870.00
(-4.16%)Baseline: 16,558.44
17,386.36
(91.28%)
binary_benchmark::marshal_group::bench_marshal_auto_many_children_allocating📈 view plot
🚷 view threshold
14,715,201.00
(-7.08%)Baseline: 15,836,009.00
16,627,809.45
(88.50%)
binary_benchmark::marshal_group::bench_marshal_exact_allocating📈 view plot
🚷 view threshold
118,358.00
(-18.53%)Baseline: 145,278.48
152,542.41
(77.59%)
binary_benchmark::marshal_group::bench_marshal_exact_huge_bytes_allocating📈 view plot
🚷 view threshold
534,378.00
(-0.09%)Baseline: 534,875.38
561,619.15
(95.15%)
binary_benchmark::marshal_group::bench_marshal_exact_long_string📈 view plot
🚷 view threshold
17,919.00
(-3.71%)Baseline: 18,609.46
19,539.93
(91.70%)
binary_benchmark::marshal_group::bench_marshal_exact_many_children_allocating📈 view plot
🚷 view threshold
28,066,347.00
(-19.48%)Baseline: 34,855,646.92
36,598,429.27
(76.69%)
binary_benchmark::marshal_group::bench_marshal_huge_bytes_allocating📈 view plot
🚷 view threshold
533,387.00
(-0.09%)Baseline: 533,892.39
560,587.01
(95.15%)
binary_benchmark::marshal_group::bench_marshal_long_string📈 view plot
🚷 view threshold
15,843.00
(-6.65%)Baseline: 16,972.51
17,821.13
(88.90%)
binary_benchmark::marshal_group::bench_marshal_many_children_allocating📈 view plot
🚷 view threshold
14,716,627.00
(-7.08%)Baseline: 15,837,167.63
16,629,026.01
(88.50%)
binary_benchmark::marshal_group::bench_marshal_reusing_buffer📈 view plot
🚷 view threshold
107,945.00
(-9.88%)Baseline: 119,778.04
125,766.94
(85.83%)
binary_benchmark::marshal_group::bench_marshal_reusing_buffer_vec_writer📈 view plot
🚷 view threshold
98,302.00
(-8.98%)Baseline: 108,003.04
113,403.19
(86.68%)
binary_benchmark::roundtrip_group::bench_roundtrip large:setup_large_marshaled()📈 view plot
🚷 view threshold
90,974.00
(-4.88%)Baseline: 95,637.12
100,418.98
(90.59%)
binary_benchmark::roundtrip_group::bench_roundtrip small:setup_small_marshaled()📈 view plot
🚷 view threshold
7,378.00
(-3.13%)Baseline: 7,616.66
7,997.49
(92.25%)
binary_benchmark::roundtrip_group::bench_roundtrip_auto large:setup_large_marshaled()📈 view plot
🚷 view threshold
91,005.00
(-1.58%)Baseline: 92,461.38
97,084.45
(93.74%)
binary_benchmark::roundtrip_group::bench_roundtrip_auto small:setup_small_marshaled()📈 view plot
🚷 view threshold
7,401.00
(+0.32%)Baseline: 7,377.61
7,746.49
(95.54%)
binary_benchmark::roundtrip_group::bench_roundtrip_exact large:setup_large_marshaled()📈 view plot
🚷 view threshold
106,790.00
(-1.35%)Baseline: 108,246.38
113,658.70
(93.96%)
binary_benchmark::roundtrip_group::bench_roundtrip_exact small:setup_small_marshaled()📈 view plot
🚷 view threshold
8,913.00
(+0.26%)Baseline: 8,889.61
9,334.09
(95.49%)
binary_benchmark::unmarshal_group::bench_unmarshal large:setup_large_marshaled()📈 view plot
🚷 view threshold
41,989.00
(-6.46%)Baseline: 44,888.98
47,133.43
(89.09%)
binary_benchmark::unmarshal_group::bench_unmarshal small:setup_small_marshaled()📈 view plot
🚷 view threshold
2,717.00
(-2.05%)Baseline: 2,773.80
2,912.49
(93.29%)
binary_benchmark::unpack_group::bench_unpack_compressed📈 view plot
🚷 view threshold
556,092.00
(+0.74%)Baseline: 551,998.21
579,598.12
(95.94%)
binary_benchmark::unpack_group::bench_unpack_uncompressed📈 view plot
🚷 view threshold
771.00
(-0.20%)Baseline: 772.57
811.20
(95.04%)
libsignal_benchmark::conversation_group::bench_full_dm_conversation full:setup_conversation_data()📈 view plot
🚷 view threshold
27,781,689.00
(+0.30%)Baseline: 27,698,403.96
29,083,324.16
(95.52%)
libsignal_benchmark::dm_group::bench_dm_decrypt_first_message decrypt_prekey:setup_dm_with_first_message()📈 view plot
🚷 view threshold
5,544,828.00
(-0.05%)Baseline: 5,547,611.30
5,824,991.87
(95.19%)
libsignal_benchmark::dm_group::bench_dm_encrypt_first_message first_msg:setup_dm_session()📈 view plot
🚷 view threshold
175,061.00
(-1.23%)Baseline: 177,239.55
186,101.53
(94.07%)
libsignal_benchmark::dm_group::bench_dm_encrypt_subsequent_message subsequent:setup_established_dm_session()📈 view plot
🚷 view threshold
175,710.00
(-1.29%)Baseline: 178,005.92
186,906.21
(94.01%)
libsignal_benchmark::dm_group::bench_dm_session_establishment setup:setup_dm_users()📈 view plot
🚷 view threshold
17,352,147.00
(+0.42%)Baseline: 17,280,337.21
18,144,354.07
(95.63%)
libsignal_benchmark::group_messaging_group::bench_group_create_distribution_message create:setup_group_sender()📈 view plot
🚷 view threshold
298,417.00
(+0.51%)Baseline: 296,900.06
311,745.06
(95.72%)
libsignal_benchmark::group_messaging_group::bench_group_decrypt_message decrypt:setup_group_with_encrypted_message()📈 view plot
🚷 view threshold
12,579,511.00
(-0.12%)Baseline: 12,595,200.21
13,224,960.22
(95.12%)
libsignal_benchmark::group_messaging_group::bench_group_encrypt_message encrypt:setup_group_with_distribution()📈 view plot
🚷 view threshold
719,597.00
(+0.35%)Baseline: 717,080.42
752,934.44
(95.57%)
libsignal_benchmark::session_optimization_group::bench_decrypt_with_previous_session previous_session:setup_with_archived_sessions()📈 view plot
🚷 view threshold
🚨 view alert (🔔)
47,129.00
(+8.22%)Baseline: 43,548.58
45,726.01
(103.07%)

libsignal_benchmark::session_optimization_group::bench_message_key_eviction eviction:setup_message_key_eviction()📈 view plot
🚷 view threshold
15,561,842.00
(+0.00%)Baseline: 15,561,771.24
16,339,859.81
(95.24%)
libsignal_benchmark::session_optimization_group::bench_out_of_order_decryption out_of_order:setup_out_of_order_messages()📈 view plot
🚷 view threshold
5,378,778.00
(-1.68%)Baseline: 5,470,485.45
5,744,009.72
(93.64%)
libsignal_benchmark::session_optimization_group::bench_promote_matching_session promote:setup_promote_matching_session()📈 view plot
🚷 view threshold
312,188.00
(-59.35%)Baseline: 768,008.93
806,409.38
(38.71%)
libsignal_benchmark::signature_group::bench_key_generation keygen📈 view plot
🚷 view threshold
2,830,547.00
(+0.16%)Baseline: 2,825,902.25
2,967,197.36
(95.39%)
libsignal_benchmark::signature_group::bench_signature_creation sign:setup_keypair_with_message()📈 view plot
🚷 view threshold
3,452,844.00
(-0.48%)Baseline: 3,469,628.00
3,643,109.40
(94.78%)
libsignal_benchmark::signature_group::bench_signature_verification verify:setup_keypair_with_message()📈 view plot
🚷 view threshold
124,998,798.00
(-0.29%)Baseline: 125,363,192.52
131,631,352.15
(94.96%)
reporting_token_benchmark::content_extraction_group::bench_content_extraction extended:setup_extended_message()📈 view plot
🚷 view threshold
12,002.00
(+1.39%)Baseline: 11,837.01
12,428.86
(96.57%)
reporting_token_benchmark::content_extraction_group::bench_content_extraction simple:setup_simple_message()📈 view plot
🚷 view threshold
3,930.00
(+2.26%)Baseline: 3,843.12
4,035.27
(97.39%)
reporting_token_benchmark::full_generation_group::bench_full_token_generation extended:setup_full_gen_extended()📈 view plot
🚷 view threshold
87,073.00
(-0.71%)Baseline: 87,696.68
92,081.52
(94.56%)
reporting_token_benchmark::full_generation_group::bench_full_token_generation simple:setup_full_gen_simple()📈 view plot
🚷 view threshold
79,022.00
(-0.88%)Baseline: 79,724.89
83,711.13
(94.40%)
reporting_token_benchmark::key_derivation_group::bench_key_derivation📈 view plot
🚷 view threshold
50,375.00
(-0.97%)Baseline: 50,867.82
53,411.21
(94.32%)
reporting_token_benchmark::message_encoding_group::bench_message_encoding extended:setup_extended_message()📈 view plot
🚷 view threshold
5,956.00
(+2.85%)Baseline: 5,791.16
6,080.72
(97.95%)
reporting_token_benchmark::message_encoding_group::bench_message_encoding simple:setup_simple_message()📈 view plot
🚷 view threshold
2,227.00
(+4.07%)Baseline: 2,139.95
2,246.95
(99.11%)
reporting_token_benchmark::token_calculation_group::bench_token_calculation📈 view plot
🚷 view threshold
21,920.00
(-0.00%)Baseline: 21,920.20
23,016.21
(95.24%)
🐰 View full continuous benchmarking report in Bencher

@coderabbitai

coderabbitai Bot commented Apr 6, 2026

Copy link
Copy Markdown

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 4865ef18-dcec-4e52-95f9-fec28e207a82

📥 Commits

Reviewing files that changed from the base of the PR and between bab74c1 and da6831b.

📒 Files selected for processing (3)
  • src/features/groups.rs
  • wacore/src/iq/business.rs
  • wacore/src/iq/groups.rs

📝 Walkthrough

Walkthrough

Added optional description ownership/timestamp and ephemeral trigger fields to group metadata and group-info structs; updated parsing/serialization to read/write these attributes and to extract description text from nested <body> nodes; introduced a Node helper to convert node content to String; adjusted business hours types.

Changes

Cohort / File(s) Summary
Group metadata & mapping
src/features/groups.rs, wacore/src/iq/groups.rs
Added description_owner: Option<Jid>, description_time: Option<u64>, and ephemeral_trigger: Option<u32>; GroupMetadata now derives Default. GroupInfoResponse updated to serialize/parse <description> attributes (participant, t, id) and <description><body> text; GroupParticipatingRequest::try_from_node now infers include flags from children.
Node content helper
wacore/binary/src/node.rs
Added pub fn content_as_string(&self) -> Option<String> to unify extraction/conversion of Node content (handles String and Bytes via UTF‑8 lossy conversion).
Stanza description parsing
wacore/src/stanza/groups.rs
Refactored description action parsing to use content_as_string() for <body> extraction (preserves previous behavior for delete/missing cases).
Business hours types
wacore/src/iq/business.rs
Changed BusinessHoursConfig.open_time and close_time from Option<String> to u32; parsing updated to parse attributes as u32 with fallback to 0.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested labels

breaking-change

Poem

🐰 I hopped through nodes and found the trace—

participant, time, a tiny place.
Bytes turned to words with a careful string,
metadata growing a sensible wing.
Cheers from the burrow for every saved trace.

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 60.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ⚠️ Warning The PR title mentions 'ephemeral trigger', 'participating roundtrip', and 'business hour types', but the actual changes only address group description parsing and add missing fields. The title references unimplemented changes. Update the title to accurately reflect the actual changes: 'fix: parse group description from body child and add description_owner/description_time fields'
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/group-description-parsing

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

- Parse `trigger` attribute from `<ephemeral>` node (WA Web extracts
  it as optional int 0-20 via InGroupsGroupInfoMixin)
- Fix GroupParticipatingRequest::try_from_node() to actually read
  child presence instead of always returning default
- Change BusinessHoursConfig open_time/close_time from Option<String>
  to u32 with default 0, matching WA Web's parseInt() behavior
@jlucaso1 jlucaso1 changed the title fix: parse group description from <body> child and add missing fields fix: group description parsing, ephemeral trigger, participating roundtrip, business hour types Apr 6, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@wacore/src/iq/groups.rs`:
- Around line 2917-2927: Update the unit test
test_group_info_response_no_description to also assert that the newly added
fields are unset: after creating response via
GroupInfoResponse::try_from_node(&node), add assertions that
response.description_owner.is_none() and response.description_time.is_none() in
addition to the existing checks for description and description_id so
regressions on the new fields are caught.
- Around line 491-507: The emission guard currently only checks description and
description_id so description_owner and description_time are dropped; update the
outer condition to emit <description> whenever any of self.description,
self.description_id, self.description_owner, or self.description_time is Some
(e.g. if self.description.is_some() || self.description_id.is_some() ||
self.description_owner.is_some() || self.description_time.is_some()), then keep
building desc_builder (using NodeBuilder) and set attrs for id, participant
(description_owner) and t (description_time) and optional body (description) so
metadata-only descriptions serialize correctly and are pushed into children.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: a7121859-2853-44c4-8e51-c70c71432208

📥 Commits

Reviewing files that changed from the base of the PR and between 0d1b5c0 and bab74c1.

📒 Files selected for processing (4)
  • src/features/groups.rs
  • wacore/binary/src/node.rs
  • wacore/src/iq/groups.rs
  • wacore/src/stanza/groups.rs

Comment thread wacore/src/iq/groups.rs Outdated
Comment thread wacore/src/iq/groups.rs
Emit <description> when any of its fields are set (not just text/id),
and assert description_owner/description_time are None in the
no-description test.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a2e5e16959

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread wacore/src/iq/business.rs
Comment on lines +182 to +184
open_time: optional_attr(c, "open_time")
.and_then(|s| s.parse::<u32>().ok())
.unwrap_or(0),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve absent business-hour times instead of defaulting to zero

In BusinessProfileSpec::parse_response, missing or non-numeric open_time values are coerced to 0 via unwrap_or(0), and the struct now stores open_time/close_time as required u32. For modes like open_24h or appointment_only where these attributes may be omitted, this turns “not provided” into a real midnight value, so downstream consumers cannot distinguish absent data from an actual 00:00 schedule and will serialize misleading times.

Useful? React with 👍 / 👎.

@jlucaso1
jlucaso1 merged commit 0a8b12b into main Apr 6, 2026
9 of 10 checks passed
@jlucaso1
jlucaso1 deleted the fix/group-description-parsing branch April 6, 2026 13:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants