Skip to content

feat: Article rich content parsing + bookmarks cursor pagination - #70

Open
zh-xl-kang wants to merge 3 commits into
public-clis:mainfrom
zh-xl-kang:feat/article-rich-content
Open

feat: Article rich content parsing + bookmarks cursor pagination#70
zh-xl-kang wants to merge 3 commits into
public-clis:mainfrom
zh-xl-kang:feat/article-rich-content

Conversation

@zh-xl-kang

@zh-xl-kang zh-xl-kang commented Jun 29, 2026

Copy link
Copy Markdown

Summary

This PR adds two enhancements:

  1. Article rich content parsing — recovers images, dividers, embedded tweets, and inline styles that were silently dropped during Markdown conversion
  2. Bookmarks cursor pagination — adds --cursor option for resumable batched fetching of bookmarks

1. Article Rich Content Parsing

Twitter Article Draft.js content has several entity types that were being silently dropped during Markdown conversion.

What was missing

Content type Before After
Images (with captions) Skipped entirely ![caption](https://pbs.twimg.com/media/xxx.jpg)
Dividers Skipped entirely ---
Embedded tweets Skipped entirely > [Embedded Tweet](https://x.com/i/status/ID)
Bold text Plain text **text**
Italic text Plain text *text*
Inline code Plain text `text`
Strikethrough Plain text ~~text~~
Bold + Link on same span Offset corruption Correct rendering

Changes — parser.py

  • _extract_atomic_markdown_extract_atomic_content: Renamed and extended to handle DIVIDER and TWEET entity types in addition to existing MARKDOWN.
  • _render_article_text_block: Rewritten to handle inlineStyleRanges (Bold/Italic/Code/Strikethrough) and entityRanges (links) in a unified right-to-left pass. Fixes offset corruption when both appear on the same text span.
  • Case-insensitive style matching: Twitter API returns style names in Title case ("Bold", "Italic"). Code normalizes via .upper().

Changes — tests/test_article_parsing.py (new)

25 unit tests covering:

  • All inline styles (Bold, Italic, Code, Strikethrough)
  • Links (including URL with parentheses)
  • Mixed Bold + Link on the same span (regression)
  • All atomic entity types (DIVIDER, TWEET, MARKDOWN)
  • End-to-end _parse_article with synthetic article data
  • Edge cases (empty text, out-of-bounds offsets, unknown entity types)

Real-world validation

Tested against 43 real Twitter Articles (310,000+ chars total). Example: a 29K-char article now recovers +2 images, +24 dividers, +3 embedded tweets, +130 bold spans, +17 links.


2. Bookmarks Cursor Pagination

Adds --cursor option to the bookmarks command for resumable batched fetching:

# First page
twitter bookmarks -n 50 --json
# → JSON output includes pagination.nextCursor

# Next page
twitter bookmarks -n 50 --cursor "NEXT_CURSOR_VALUE" --json

Changes — client.py

  • fetch_bookmarks() now accepts cursor and return_cursor parameters, delegates to _fetch_timeline() which already supports cursor pagination.

Changes — cli.py

  • New --cursor CLI option on bookmarks command.
  • _run_bookmarks_command() refactored to inline the fetch logic (previously delegated to _fetch_and_display), passing cursor through and emitting pagination.nextCursor in the structured output envelope.

Test results

126 passed in 0.26s
├── 25 new (article parsing)
└── 101 existing (zero regressions)

…tyles from Articles

Twitter Article Draft.js content has several entity types that were being
silently dropped during Markdown conversion:

- MEDIA entities → now rendered as `![caption](url)` with caption text
- DIVIDER entities → now rendered as `---`
- TWEET entities → now rendered as `> [Embedded Tweet](url)`
- inlineStyleRanges (Bold/Italic/Code/Strikethrough) → now converted to
  `**`, `*`, backticks, `~~` respectively

The previous implementation skipped all atomic blocks (images, dividers,
embedded tweets) and ignored inline style ranges entirely.

Key design decisions:
- Style and link operations are collected as (start, end, replacement)
  tuples and applied right-to-left, so overlapping Bold+Link on the same
  text block don't corrupt each other's character offsets.
- Twitter API returns style names in Title case ('Bold', 'Italic') rather
  than uppercase, so comparisons use .upper() for case-insensitive matching.
- Image URLs are resolved from article_results.media_entities using the
  mediaId → original_img_url mapping chain (entityMap only contains mediaId,
  not the URL itself).

Tested against 43 real Twitter Articles (310K+ chars total).
25 new unit tests added, 101 existing tests still pass.
…_content

The function was renamed from _extract_atomic_markdown to _extract_atomic_content.
Update all references in the existing test suite.
@zh-xl-kang

Copy link
Copy Markdown
Author

Hi! Just a gentle bump on this PR.

Quick summary: The Draft.js article parser was silently dropping images, dividers, embedded tweets, and all inline formatting (Bold/Italic/Code). This PR recovers all of them.

What changed (parser.py, +70/-29 lines):

  • _extract_atomic_markdown renamed to _extract_atomic_content: now handles DIVIDER, TWEET, in addition to existing MARKDOWN
  • _render_article_text_block: rewritten to handle inlineStyleRanges (Bold/Italic/Code/Strikethrough) and links in a unified right-to-left pass, fixing offset corruption when both appear on the same text span
  • Image URLs resolved via media_entities mapping chain (mediaId to original_img_url)

Validation: tested against 43 real Twitter Articles (310K+ chars). Example: a 29K-char article now recovers +2 images, +24 dividers, +3 embedded tweets, +130 bold spans, +17 links.

Tests: 25 new + 101 existing = 126 passed, 0 failures. Also updated test_client.py imports for the renamed function.

I noticed the CI workflow has not run yet — it may need maintainer approval for fork PRs. Happy to address any feedback. Thanks for the great tool!

@zh-xl-kang zh-xl-kang changed the title feat(parser): extract images, dividers, embedded tweets, and inline styles from Articles feat: Article rich content parsing + bookmarks cursor pagination Jul 16, 2026

@LuciusChen LuciusChen left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

There is a blocking overlap bug in the new article renderer. When a LINK entity and Bold style cover the same original span, the first replacement changes the string length and the second replacement still slices using original offsets. A four-character bold link currently renders as herere** instead of nested Markdown.

The existing test_bold_and_link_mixed uses adjacent, non-overlapping ranges (Bold on Click, link on here), so it does not exercise the case described by the PR. Please add same-span and nested/partially-overlapping style+entity tests and render from shared boundaries rather than applying whole-span replacements to an already-mutated string.

The bookmarks cursor addition also needs client and CLI regression tests verifying cursor input and pagination.nextCursor output before it is safe to merge.

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.

2 participants