Skip to content

Capture media-tagged accounts (media.features.all.tags) — currently dropped from media parsing #152

@RealADemin

Description

@RealADemin

ft's media parser at src/graphql-bookmarks.ts:272-288 pulls seven fields from each media object: type, url, expandedUrl, width, height, altText, videoVariants. It drops media.features.all.tags — the list of accounts an author tagged in a photo, which X renders as overlay chips below the media on x.com.

For affiliate/maker/review/product tweets where the relevant brand or person is tagged on the photo rather than @-mentioned in the text, this is the only signal that the account is associated with the bookmark. Without it, downstream consumers can't surface the relationship: text parsing finds nothing, the bookmark's @-mentions array is empty, the bio-mention path is author-level not tweet-level.

Field shape (verified May 2026)

Each tagged user is an object on media.features.all.tags[]:

{
  "name": "<display name>",
  "screen_name": "<@handle>",
  "type": "user",
  "user_id": "<numeric id>"
}

Path: legacy.extended_entities.media[i].features.all.tags[] — the same media array ft already iterates at line 272. Same path applies to quoted-tweet media at quoted_status_result.result.legacy.extended_entities.media[i].features.all.tags[].

Photo-only — videos use a different shape

Verified across three sampled tweets containing tagged media (one outer photo, one quoted photo, one quoted video):

  • Photos carry features.all.tags[] populated when accounts are tagged. They also carry features.{small,medium,large,orig}.faces[] for face-crop detection — unrelated to user tagging.
  • Videos don't have a features field at all. They expose source_user_id_str and source_status_id_str instead, which mark the original poster when a video has been reposted (attribution-to-creator, not tagged-users). Different concept; out of scope for this issue.

So this issue is specifically about photo tagging. If X supports video tagging it's in a separate code path with separate field shape.

Suggested scope

Extend the mediaObjects.map block at src/graphql-bookmarks.ts:276-288 to also extract features.all.tags. Extend BookmarkMediaObject (src/types.ts:7-19) with a new optional field.

Roughly:

// types.ts — add to BookmarkMediaObject
taggedUsers?: { name: string; screenName: string; userId: string }[];

// graphql-bookmarks.ts — add inside the mediaObjects.map at ~L287
taggedUsers: (m.features?.all?.tags ?? [])
  .filter((t: any) => t?.type === 'user')
  .map((t: any) => ({ name: t.name, screenName: t.screen_name, userId: t.user_id })),

Naming, exact field shape, persistence approach — all maintainer's choice. If mediaObjects is stored as a JSON column (same pattern as quoted_tweet_json), adding the field is automatic — no schema migration. If individual columns, this would add one per-media-item TEXT column or similar.

The type === "user" filter is defensive; in all three sampled responses every tag entry had type: "user", but X may add other tag types (locations? products?) and downstream consumers presumably only want user-tags.

Why this matters for downstream

A common pattern on X: investor/maker/review tweets where the relevant brand or maker account is tagged under the photo rather than typed into the body. For these tweets, the photo-tag is the only signal that the bookmark is "about" that account. Without surfacing the tag in ft's data:

  • Search by handle doesn't find the bookmark (handle isn't in text)
  • Filter-by-account dimension doesn't include the tagged account
  • GUIs can't render the tag chips X displays natively below the media
  • The relationship "this tweet is about @x" is lost even though X surfaces it visually

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions