Skip to content

feat(user): return PictureURL on POST /user/info#121

Open
cesar-carlos wants to merge 7 commits into
evolution-foundation:mainfrom
cesar-carlos:feat/user-info-picture-url
Open

feat(user): return PictureURL on POST /user/info#121
cesar-carlos wants to merge 7 commits into
evolution-foundation:mainfrom
cesar-carlos:feat/user-info-picture-url

Conversation

@cesar-carlos

@cesar-carlos cesar-carlos commented Jul 17, 2026

Copy link
Copy Markdown

Description

POST /user/info never returned a profile picture URL (only PictureID) and, with a +-prefixed JID, also failed with usync "info query timed out" — same root cause as #76 / #120.

What changed

  1. Canonical JID for GetUserInfo — strip + via CanonicalJID + ToNonAD before the usync query.
  2. Additive field PictureURL on UserInfo — best-effort preview URL after GetUserInfo (5s timeout). Failures leave PictureURL empty and do not fail the whole /info response.
  3. Shared private helper fetchProfilePicture (used by /user/avatar and /user/info). Does not pass ExistingID.
  4. Addendum 18/jul (production Chatwoot report):
    • Map usync rate-overlimitHTTP 429; IQ/context timeout → HTTP 504
    • Bound usync to 10s via request context
    • Resolve @lid → PN via Store.LIDs.GetPNForLID before usync when available (P3 consistency)
    • Wiki: canonical query order @lid → PN JID → digits; document 429/504

Response shape (additive, non-breaking)

{
  "PictureID": "57501569",
  "PictureURL": "https://pps.whatsapp.net/v/..."
}

For full-resolution image, clients should still use POST /user/avatar with preview: false.

Related Issue

Related to #76
Depends on #120 (this branch includes those fix commits; rebase after #120 merges if needed)

Type of Change

  • New feature (non-breaking change which adds functionality)
  • Bug fix (rate-limit status + LID usync consistency)

Testing

Checklist

  • My code follows the project's style guidelines
  • I have performed a self-review of my code
  • I have tested my changes thoroughly
  • Docs updated for the new response field and canonical query order

Additional Notes

Please review/merge #120 first if you prefer the bugfix alone; this PR stacks on that branch.

CreateJID/ParseJID prefixes phone numbers with "+", which breaks the
raw GetProfilePictureInfo IQ (Target=jid) and surfaces as
"info query timed out" after ~75s on POST /user/avatar.

Strip the prefix via CanonicalJID and ToNonAD before the request,
matching the existing fix for reactions/typing/receipts.

Closes evolution-foundation#76

Co-authored-by: Cursor <cursoragent@cursor.com>
@sourcery-ai

sourcery-ai Bot commented Jul 17, 2026

Copy link
Copy Markdown

Reviewer's Guide

Adds a PictureURL field to POST /user/info responses and fixes JID canonicalization for usync and profile picture IQs by introducing a shared profile-picture helper and updating API docs.

Sequence diagram for POST /user/info with PictureURL enrichment

sequenceDiagram
    actor Client
    participant ApiUserInfoHandler
    participant UserService
    participant WhatsmeowClient

    Client->>ApiUserInfoHandler: POST /user/info
    ApiUserInfoHandler->>UserService: GetUser
    UserService->>UserService: utils.CanonicalJID + ToNonAD
    UserService->>WhatsmeowClient: GetUserInfo(jids)
    WhatsmeowClient-->>UserService: whatsmeowInfo
    UserService->>UserService: fetchProfilePicture(client, jid, true, 25s)
    UserService->>WhatsmeowClient: GetProfilePictureInfo(jid, preview)
    WhatsmeowClient-->>UserService: ProfilePictureInfo(URL)
    UserService-->>ApiUserInfoHandler: UserInfo{PictureID, PictureURL}
    ApiUserInfoHandler-->>Client: 200 OK with PictureURL
Loading

File-Level Changes

Change Details Files
Return a best-effort PictureURL in POST /user/info without affecting error behavior.
  • Extended UserInfo struct to include a PictureURL string field.
  • After GetUserInfo, call a profile picture helper per JID with a 25s timeout to fetch a preview picture URL.
  • Log a warning on profile picture lookup failure but keep the /user/info response successful, leaving PictureURL empty.
  • Populate PictureURL in the returned UserInfo from the fetched profile picture when available.
pkg/user/service/user_service.go
Fix JID handling for usync and profile-picture IQs by using a canonical digits-only JID before WhatsApp client calls.
  • When building JIDs for GetUserInfo, convert the parsed JID with utils.CanonicalJID(...).ToNonAD() to strip any '+' and normalize.
  • In GetAvatar, normalize the JID via utils.CanonicalJID(...).ToNonAD() before calling the WhatsApp profile picture API, with comments documenting the previous '+'-prefix issue.
pkg/user/service/user_service.go
Introduce a shared fetchProfilePicture helper for profile picture retrieval used by both /user/info enrichment and /user/avatar.
  • Added fetchProfilePicture method on userService that normalizes the JID, applies a context timeout, and calls client.GetProfilePictureInfo.
  • Configured the helper to never pass ExistingID so unchanged pictures yield nil without error, preserving expected whatsmeow behavior.
  • Refactored GetAvatar to delegate to fetchProfilePicture with an 80s timeout instead of duplicating context and GetProfilePictureInfo logic.
pkg/user/service/user_service.go
Update user API documentation to document PictureURL in /user/info responses and refresh the example cURL.
  • Documented PictureURL as a preview profile picture URL and clarified that full-resolution images require POST /user/avatar.
  • Updated the JSON response example for /user/info to include PictureURL.
  • Adjusted the example cURL command to use SERVER_PORT interpolation instead of a hardcoded port.
docs/wiki/guias-api/api-user.md

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai 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.

Hey - I've found 1 issue, and left some high level feedback:

  • The 25s and 80s timeouts for profile picture lookups are hard-coded in different places; consider centralizing them as named constants to keep behavior consistent and easier to tune.
  • The warning log on failed PictureURL enrichment in GetUser may become noisy under partial network issues; consider lowering this to debug or adding sampling if failures are expected to be common.
  • The CanonicalJID().ToNonAD normalization is now applied in multiple flows; it might be helpful to wrap this into a single helper (e.g., canonicalUserJID/jidForProfilePicture) to avoid future divergences in JID handling.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The 25s and 80s timeouts for profile picture lookups are hard-coded in different places; consider centralizing them as named constants to keep behavior consistent and easier to tune.
- The warning log on failed PictureURL enrichment in GetUser may become noisy under partial network issues; consider lowering this to debug or adding sampling if failures are expected to be common.
- The CanonicalJID().ToNonAD normalization is now applied in multiple flows; it might be helpful to wrap this into a single helper (e.g., canonicalUserJID/jidForProfilePicture) to avoid future divergences in JID handling.

## Individual Comments

### Comment 1
<location path="pkg/user/service/user_service.go" line_range="332-341" />
<code_context>
+// fetchProfilePicture requests a profile picture URL for jid.
+// Never pass ExistingID here: when the picture is unchanged whatsmeow returns
+// nil with no error and no URL.
+func (u *userService) fetchProfilePicture(client *whatsmeow.Client, jid types.JID, preview bool, timeout time.Duration) (*types.ProfilePictureInfo, error) {
+	jid = utils.CanonicalJID(jid).ToNonAD()
+	ctx, cancel := context.WithTimeout(context.Background(), timeout)
+	defer cancel()
+
+	pic, err := client.GetProfilePictureInfo(ctx, jid, &whatsmeow.GetProfilePictureParams{
+		Preview: preview,
+	})
+	if err != nil {
+		return nil, fmt.Errorf("get profile picture for %s: %w", jid, err)
+	}
+	return pic, nil
+}
+
</code_context>
<issue_to_address>
**suggestion:** Consider centralizing timeout values instead of using magic numbers at call sites.

Callers currently pass literals like `25*time.Second` and `80*time.Second` into `timeout`. If these reflect distinct timeout policies, consider defining named constants or configuration options for them so they’re easier to understand and adjust without searching through all call sites.

Suggested implementation:

```golang
const (
	profilePicturePreviewTimeout = 25 * time.Second
	profilePictureFullTimeout    = 80 * time.Second
)

// fetchProfilePicture requests a profile picture URL for jid.
// Never pass ExistingID here: when the picture is unchanged whatsmeow returns
// nil with no error and no URL.

```

```golang
		pictureURL := ""
		pic, picErr := u.fetchProfilePicture(client, jid, true, profilePicturePreviewTimeout)
		if picErr != nil {
			u.loggerWrapper.GetLogger(instance.Id).LogWarn("[%s] Failed to enrich PictureURL for %s: %v", instance.Id, jid, picErr)
		} else if pic != nil {
			pictureURL = pic.URL
		}

```

1. Replace any other calls that currently pass `80*time.Second` as the timeout into `fetchProfilePicture` with `profilePictureFullTimeout` to align with the new constants.
2. If your project prefers configuration-driven timeouts, you can later wire these constants to configuration values (e.g., loaded from env/config) rather than hard-coded durations, while keeping the constant names as the single source of truth for callers.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread pkg/user/service/user_service.go Outdated
cesar-carlos added a commit to cesar-carlos/evolution-go that referenced this pull request Jul 17, 2026
@cesar-carlos

Copy link
Copy Markdown
Author

Closing from the author side: these changes are already merged into our fork main for production use.

The commits remain available on the PR branch if maintainers still want to review/merge into evolution-foundation/evolution-go. Feel free to reopen if preferred.

@cesar-carlos
cesar-carlos deleted the feat/user-info-picture-url branch July 17, 2026 20:08
@cesar-carlos
cesar-carlos restored the feat/user-info-picture-url branch July 17, 2026 20:09
@cesar-carlos cesar-carlos reopened this Jul 17, 2026
@cesar-carlos

Copy link
Copy Markdown
Author

Reopened — keeping this PR available for upstream review/merge.

Cap GetProfilePictureInfo at 8s using the request context so clients
with ~12s read timeouts get HTTP 504 instead of a hung connection.
Map WhatsApp rate-overlimit to HTTP 429.

Co-authored-by: Cursor <cursoragent@cursor.com>
@cesar-carlos

Copy link
Copy Markdown
Author

Updated after production avatar/info failure report (Chatwoot enrichment):

Please re-review the new commits on this branch.

cesar-carlos and others added 5 commits July 18, 2026 15:10
Prefer PN JID when the LID store has a mapping before the profile
picture IQ. Replace fixed Sleep(2s) after StartInstance with a
context-aware ready poll so avatar timeouts are not wasted on wait.
Map context.Canceled to HTTP 504 like other request aborts.

Co-authored-by: Cursor <cursoragent@cursor.com>
POST /user/info only returned PictureID and timed out on usync when the
JID still had a "+" prefix. Canonicalize JIDs for GetUserInfo and enrich
each user with a best-effort preview PictureURL (no ExistingID), sharing
a small fetchProfilePicture helper with /user/avatar.

Related to evolution-foundation#76

Co-authored-by: Cursor <cursoragent@cursor.com>
Use request context with a 10s usync deadline, shorten PictureURL
enrich to 5s, map rate-overlimit/timeouts via writeUserWAError, and
resolve @lid to PN via the LID store before usync when available.

Co-authored-by: Cursor <cursoragent@cursor.com>
Prefer @lid then PN JID then digits; document HTTP 429/504 and fix
avatar curl examples to use SERVER_PORT.

Co-authored-by: Cursor <cursoragent@cursor.com>
Share a 5s wall-clock budget across all users after usync, skip enrich
when PictureID is empty, and stop further enrich after rate-overlimit.
Use context-aware client ready wait for GetUser as well.

Co-authored-by: Cursor <cursoragent@cursor.com>
@cesar-carlos
cesar-carlos force-pushed the feat/user-info-picture-url branch from 077d662 to e323425 Compare July 18, 2026 15:12
@cesar-carlos

Copy link
Copy Markdown
Author

Follow-up improvements from review (rebased on updated #120):

  • PictureURL enrich: shared 5s wall-clock budget (not 5s × N)
  • Skip enrich when usync PictureID is empty
  • Stop further enrich after rate-overlimit
  • GetUser also uses context-aware client ready wait

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