xapi-to 0.1.18 → 0.1.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,315 @@
1
+ # 小红书 (Xiaohongshu / RedNote) Guide
2
+
3
+ Complete guide for Xiaohongshu (小红书) operations via xAPI — user profiles, notes, comments, search, topics, products, and creator inspiration.
4
+
5
+ > **Dynamic catalog:** These are database-registered third-party APIs under the `xiaohongshu` service. Exact action IDs, HTTP methods, parameters, and response fields can change. Run `search` and `get` before calling; the current schema wins. Examples below reflect one known GET-based version and keep `"method":"GET"` in the input for compatibility.
6
+
7
+ **Tip:** Many endpoints accept both a direct ID (e.g. `user_id`, `note_id`) and a `share_text` (share link). You can use either one.
8
+
9
+ ## Contents
10
+
11
+ - [User data](#user-data)
12
+ - [Note data](#note-data)
13
+ - [Search](#search)
14
+ - [Topics](#topics)
15
+ - [Products](#products-商品)
16
+ - [Creator inspiration](#creator-inspiration-创作灵感)
17
+ - [Common workflows](#common-workflows)
18
+ - [Pagination](#pagination-patterns)
19
+ - [API reference](#api-reference)
20
+ - [Error handling](#error-handling)
21
+
22
+ ## User Data
23
+
24
+ ### Get user info
25
+
26
+ ```bash
27
+ npx xapi-to call xiaohongshu.api_v1_xiaohongshu_app__v2_get__user__info \
28
+ --input '{"method":"GET","params":{"user_id":"<user_id>"}}'
29
+ ```
30
+
31
+ Can also pass `share_text` (share link) instead of `user_id`.
32
+
33
+ ### Get user's posted notes
34
+
35
+ ```bash
36
+ npx xapi-to call xiaohongshu.api_v1_xiaohongshu_app__v2_get__user__posted__notes \
37
+ --input '{"method":"GET","params":{"user_id":"<user_id>"}}'
38
+ ```
39
+
40
+ Optional parameters:
41
+ - `cursor` — pagination cursor, leave empty for first request; pass last `note_id` from previous response
42
+ - `share_text` — can use share link instead of `user_id`
43
+
44
+ ### Get user's faved notes
45
+
46
+ ```bash
47
+ npx xapi-to call xiaohongshu.api_v1_xiaohongshu_app__v2_get__user__faved__notes \
48
+ --input '{"method":"GET","params":{"user_id":"<user_id>"}}'
49
+ ```
50
+
51
+ Optional parameters:
52
+ - `cursor` — pagination cursor, pass last `note_id` from previous response
53
+ - `share_text` — share link alternative
54
+
55
+ ## Note Data
56
+
57
+ ### Get image note detail
58
+
59
+ ```bash
60
+ npx xapi-to call xiaohongshu.api_v1_xiaohongshu_app__v2_get__image__note__detail \
61
+ --input '{"method":"GET","params":{"note_id":"<note_id>"}}'
62
+ ```
63
+
64
+ Use this for image-type notes. Can also pass `share_text` instead of `note_id`.
65
+
66
+ ### Get video note detail
67
+
68
+ ```bash
69
+ npx xapi-to call xiaohongshu.api_v1_xiaohongshu_app__v2_get__video__note__detail \
70
+ --input '{"method":"GET","params":{"note_id":"<note_id>"}}'
71
+ ```
72
+
73
+ Use this for video-type notes. Can also pass `share_text` instead of `note_id`.
74
+
75
+ ### Get mixed note detail
76
+
77
+ ```bash
78
+ npx xapi-to call xiaohongshu.api_v1_xiaohongshu_app__v2_get__mixed__note__detail \
79
+ --input '{"method":"GET","params":{"note_id":"<note_id>"}}'
80
+ ```
81
+
82
+ Auto-detects note type (image or video) from feed. Can also pass `share_text` instead of `note_id`.
83
+
84
+ **Tip:** If you don't know whether a note is image or video, use `get__mixed__note__detail` — it handles both types.
85
+
86
+ ### Get note comments
87
+
88
+ ```bash
89
+ npx xapi-to call xiaohongshu.api_v1_xiaohongshu_app__v2_get__note__comments \
90
+ --input '{"method":"GET","params":{"note_id":"<note_id>","index":0}}'
91
+ ```
92
+
93
+ Optional parameters:
94
+ - `index` — comment index, pass `0` for first request
95
+ - `cursor` — pagination cursor, leave empty for first request
96
+ - `sort_strategy` — `default`, `latest_v2` (最新), `like_count` (最热)
97
+ - `share_text` — share link alternative
98
+
99
+ ## Search
100
+
101
+ ### Search notes
102
+
103
+ ```bash
104
+ npx xapi-to call xiaohongshu.api_v1_xiaohongshu_app__v2_search__notes \
105
+ --input '{"method":"GET","params":{"keyword":"咖啡推荐","page":1}}'
106
+ ```
107
+
108
+ Optional parameters:
109
+ - `page` — page number, start from 1
110
+ - `note_type` — `不限` (all), `视频笔记` (video), `普通笔记` (image), `直播笔记` (live)
111
+ - `sort_type` — sort type
112
+ - `time_filter` — `不限` (all), `一天内` (1 day), `一周内` (1 week), `半年内` (6 months)
113
+ - `ai_mode` — `0` = off, `1` = on (AI-enhanced search)
114
+ - `search_id` — for pagination consistency
115
+ - `search_session_id` — for pagination consistency
116
+
117
+ ### Search users
118
+
119
+ ```bash
120
+ npx xapi-to call xiaohongshu.api_v1_xiaohongshu_app__v2_search__users \
121
+ --input '{"method":"GET","params":{"keyword":"美食博主","page":1}}'
122
+ ```
123
+
124
+ Optional: `page`, `search_id`.
125
+
126
+ ### Search images
127
+
128
+ ```bash
129
+ npx xapi-to call xiaohongshu.api_v1_xiaohongshu_app__v2_search__images \
130
+ --input '{"method":"GET","params":{"keyword":"壁纸","page":1}}'
131
+ ```
132
+
133
+ Optional: `page`, `search_id`, `word_request_id`, `search_session_id`.
134
+
135
+ ### Search groups
136
+
137
+ ```bash
138
+ npx xapi-to call xiaohongshu.api_v1_xiaohongshu_app__v2_search__groups \
139
+ --input '{"method":"GET","params":{"keyword":"摄影","page_no":0}}'
140
+ ```
141
+
142
+ Optional parameters:
143
+ - `page_no` — page number, start from **0** (not 1)
144
+ - `is_recommend` — `0` = no, `1` = yes
145
+ - `search_id` — for pagination
146
+
147
+ ### Search products
148
+
149
+ ```bash
150
+ npx xapi-to call xiaohongshu.api_v1_xiaohongshu_app__v2_search__products \
151
+ --input '{"method":"GET","params":{"keyword":"面霜","page":1}}'
152
+ ```
153
+
154
+ Optional: `page`, `search_id`.
155
+
156
+ ## Topics
157
+
158
+ ### Get topic info
159
+
160
+ ```bash
161
+ npx xapi-to call xiaohongshu.api_v1_xiaohongshu_app__v2_get__topic__info \
162
+ --input '{"method":"GET","params":{"page_id":"<topic_page_id>"}}'
163
+ ```
164
+
165
+ Optional: `note_id` — pass when jumping from a specific note to its topic.
166
+
167
+ ### Get topic feed
168
+
169
+ ```bash
170
+ npx xapi-to call xiaohongshu.api_v1_xiaohongshu_app__v2_get__topic__feed \
171
+ --input '{"method":"GET","params":{"page_id":"<topic_page_id>","sort":"trend"}}'
172
+ ```
173
+
174
+ Optional parameters:
175
+ - `sort` — `trend` (最热), `time` (最新)
176
+ - `cursor_score` — pagination cursor score for next page
177
+ - `last_note_id` — last note ID from previous page
178
+ - `last_note_ct` — last note create time from previous page
179
+ - `session_id` — keep consistent across pagination
180
+ - `first_load_time` — keep consistent across pagination
181
+
182
+ ## Products (商品)
183
+
184
+ ### Get product detail
185
+
186
+ ```bash
187
+ npx xapi-to call xiaohongshu.api_v1_xiaohongshu_app__v2_get__product__detail \
188
+ --input '{"method":"GET","params":{"sku_id":"<sku_id>"}}'
189
+ ```
190
+
191
+ ### Get product reviews
192
+
193
+ ```bash
194
+ npx xapi-to call xiaohongshu.api_v1_xiaohongshu_app__v2_get__product__reviews \
195
+ --input '{"method":"GET","params":{"sku_id":"<sku_id>","page":0}}'
196
+ ```
197
+
198
+ Optional parameters:
199
+ - `page` — page number, start from **0**
200
+ - `sort_strategy_type` — `0` = general, `1` = latest
201
+ - `share_pics_only` — `0` = all reviews, `1` = only reviews with images
202
+
203
+ ### Get product review overview
204
+
205
+ ```bash
206
+ npx xapi-to call xiaohongshu.api_v1_xiaohongshu_app__v2_get__product__review__overview \
207
+ --input '{"method":"GET","params":{"sku_id":"<sku_id>"}}'
208
+ ```
209
+
210
+ Optional: `tab`.
211
+
212
+ ### Get product recommendations
213
+
214
+ ```bash
215
+ npx xapi-to call xiaohongshu.api_v1_xiaohongshu_app__v2_get__product__recommendations \
216
+ --input '{"method":"GET","params":{"sku_id":"<sku_id>"}}'
217
+ ```
218
+
219
+ Optional: `cursor_score` for pagination, `region`.
220
+
221
+ ## Creator Inspiration (创作灵感)
222
+
223
+ ### Get creator inspiration feed
224
+
225
+ ```bash
226
+ npx xapi-to call xiaohongshu.api_v1_xiaohongshu_app__v2_get__creator__inspiration__feed \
227
+ --input '{"method":"GET","params":{}}'
228
+ ```
229
+
230
+ Optional parameters:
231
+ - `tab` — tab type
232
+ - `cursor` — pagination cursor
233
+
234
+ ### Get creator hot inspiration feed
235
+
236
+ ```bash
237
+ npx xapi-to call xiaohongshu.api_v1_xiaohongshu_app__v2_get__creator__hot__inspiration__feed \
238
+ --input '{"method":"GET","params":{}}'
239
+ ```
240
+
241
+ Optional: `cursor` for pagination.
242
+
243
+ ## Common Workflows
244
+
245
+ ### Research a Xiaohongshu user
246
+
247
+ 1. Get user info: `xiaohongshu...get__user__info` with `user_id` → profile, follower stats
248
+ 2. Get posted notes: `xiaohongshu...get__user__posted__notes` → browse content
249
+ 3. Get faved notes: `xiaohongshu...get__user__faved__notes` → what they like
250
+
251
+ ### Analyze a note
252
+
253
+ 1. Get note detail: `xiaohongshu...get__mixed__note__detail` with `note_id` → full note data
254
+ 2. Get comments: `xiaohongshu...get__note__comments` with `sort_strategy=like_count` → top comments
255
+
256
+ ### Discover content by topic
257
+
258
+ 1. Get topic info: `xiaohongshu...get__topic__info` with `page_id` → topic metadata
259
+ 2. Get topic feed: `xiaohongshu...get__topic__feed` with `sort=trend` → trending notes in topic
260
+
261
+ ### Research a product
262
+
263
+ 1. Get product detail: `xiaohongshu...get__product__detail` with `sku_id` → product info
264
+ 2. Get review overview: `xiaohongshu...get__product__review__overview` → rating summary
265
+ 3. Get reviews: `xiaohongshu...get__product__reviews` → detailed reviews
266
+ 4. Get recommendations: `xiaohongshu...get__product__recommendations` → similar products
267
+
268
+ ### Search content
269
+
270
+ 1. Search notes: `xiaohongshu...search__notes` with `keyword` → find relevant notes
271
+ 2. Search users: `xiaohongshu...search__users` → find creators
272
+ 3. Search products: `xiaohongshu...search__products` → find products
273
+
274
+ ## Pagination Patterns
275
+
276
+ | Pattern | Endpoints | How to use |
277
+ |---------|-----------|------------|
278
+ | `cursor` (note_id) | user posted/faved notes | Pass last `note_id` from previous response |
279
+ | `cursor` | comments, creator feed, product recommendations | Pass `cursor` from previous response |
280
+ | `page` (from 1) | search notes/users/images/products | Increment `page` by 1 |
281
+ | `page_no` (from 0) | search groups | Increment `page_no` by 1 |
282
+ | `page` (from 0) | product reviews | Increment `page` by 1 |
283
+ | `cursor_score` + `last_note_id` | topic feed | Pass both from previous response |
284
+
285
+ ## API Reference
286
+
287
+ | API (prefix: `xiaohongshu.api_v1_xiaohongshu_app__v2_`) | Description | Key Params |
288
+ |---|---|---|
289
+ | `get__user__info` | User profile | `user_id` or `share_text` |
290
+ | `get__user__posted__notes` | User's notes | `user_id`, `cursor` |
291
+ | `get__user__faved__notes` | User's favorites | `user_id`, `cursor` |
292
+ | `get__image__note__detail` | Image note detail | `note_id` or `share_text` |
293
+ | `get__video__note__detail` | Video note detail | `note_id` or `share_text` |
294
+ | `get__mixed__note__detail` | Mixed note detail (auto-detect) | `note_id` or `share_text` |
295
+ | `get__note__comments` | Note comments | `note_id`, `index`, `cursor`, `sort_strategy` |
296
+ | `search__notes` | Search notes | `keyword`*, `page`, `note_type`, `time_filter` |
297
+ | `search__users` | Search users | `keyword`*, `page` |
298
+ | `search__images` | Search images | `keyword`*, `page` |
299
+ | `search__groups` | Search groups | `keyword`*, `page_no` |
300
+ | `search__products` | Search products | `keyword`*, `page` |
301
+ | `get__topic__info` | Topic info | `page_id`* |
302
+ | `get__topic__feed` | Topic feed | `page_id`*, `sort`, `cursor_score` |
303
+ | `get__product__detail` | Product detail | `sku_id`* |
304
+ | `get__product__reviews` | Product reviews | `sku_id`*, `page`, `sort_strategy_type` |
305
+ | `get__product__review__overview` | Product review overview | `sku_id`* |
306
+ | `get__product__recommendations` | Product recommendations | `sku_id`*, `cursor_score` |
307
+ | `get__creator__inspiration__feed` | Creator inspiration feed | `tab`, `cursor` |
308
+ | `get__creator__hot__inspiration__feed` | Creator hot inspiration | `cursor` |
309
+
310
+ ## Error Handling
311
+
312
+ - **Missing note_id** → Use `search__notes` to find notes, or parse `note_id` from share links
313
+ - **Image vs Video note** → If unsure, use `get__mixed__note__detail` which auto-detects
314
+ - **Pagination: page starts from 0 or 1** → Check endpoint docs: search notes/users/images start from `1`, search groups and product reviews start from `0`
315
+ - **Empty results** → Verify the `user_id`, `note_id`, or `sku_id` is valid
@@ -0,0 +1,125 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ usage() {
5
+ echo "Usage: $0 <tweet-url-or-id> [output-directory]" >&2
6
+ }
7
+
8
+ die() {
9
+ echo "Error: $*" >&2
10
+ exit 1
11
+ }
12
+
13
+ for required_command in npx jq curl file mktemp; do
14
+ command -v "$required_command" >/dev/null 2>&1 ||
15
+ die "required command not found: $required_command"
16
+ done
17
+
18
+ TWEET_INPUT=${1:-}
19
+ OUTPUT_DIR=${2:-.}
20
+ [ -n "$TWEET_INPUT" ] || {
21
+ usage
22
+ exit 2
23
+ }
24
+ [ -d "$OUTPUT_DIR" ] || die "output directory does not exist: $OUTPUT_DIR"
25
+ OUTPUT_DIR=$(cd "$OUTPUT_DIR" && pwd -P)
26
+
27
+ if [[ "$TWEET_INPUT" =~ ^https://((www|mobile)\.)?(x\.com|twitter\.com)/[^/]+/status/([0-9]+) ]]; then
28
+ TWEET_ID=${BASH_REMATCH[4]}
29
+ elif [[ "$TWEET_INPUT" =~ ^[0-9]+$ ]]; then
30
+ TWEET_ID=$TWEET_INPUT
31
+ else
32
+ die "expected a numeric tweet ID or an x.com/twitter.com status URL"
33
+ fi
34
+
35
+ SCHEMA=$(npx xapi-to get twitter.tweet_detail --format json)
36
+ printf '%s' "$SCHEMA" | jq -e '
37
+ .output.properties.data.properties.tweet.properties.media
38
+ .items.properties.video_url.type == "string"
39
+ ' >/dev/null || die "twitter.tweet_detail video_url is not deployed on this xAPI backend"
40
+
41
+ INPUT=$(jq -nc --arg tweet_id "$TWEET_ID" '{tweet_id:$tweet_id}')
42
+ RESPONSE=$(npx xapi-to call twitter.tweet_detail --input "$INPUT" --format json)
43
+
44
+ DOWNLOADS=$(printf '%s' "$RESPONSE" | jq -cer '
45
+ def tweet_tree:
46
+ .,
47
+ (
48
+ (.quoted_tweet?, .retweeted_tweet?)
49
+ | select(type == "object")
50
+ | tweet_tree
51
+ );
52
+
53
+ if .success != true then
54
+ error("xAPI call failed")
55
+ elif (.data.tweet | type) != "object" then
56
+ error("tweet detail returned no main tweet")
57
+ else
58
+ [
59
+ .data.tweet
60
+ | tweet_tree
61
+ | .media[]?
62
+ | select(.type == "video" or .type == "animated_gif")
63
+ | select((.video_url? | type) == "string" and (.video_url | length) > 0)
64
+ | {url: .video_url}
65
+ ]
66
+ | unique_by(.url)
67
+ | if length == 0 then error("tweet has no downloadable MP4") else . end
68
+ end
69
+ ')
70
+
71
+ COUNT=$(printf '%s' "$DOWNLOADS" | jq -r 'length')
72
+ URLS=()
73
+ OUTPUTS=()
74
+ TEMP_FILES=()
75
+
76
+ for ((INDEX = 0; INDEX < COUNT; INDEX++)); do
77
+ VIDEO_URL=$(printf '%s' "$DOWNLOADS" | jq -r --argjson index "$INDEX" '.[$index].url')
78
+ [[ "$VIDEO_URL" =~ ^https://video\.twimg\.com/ ]] ||
79
+ die "refusing unexpected media URL outside https://video.twimg.com/"
80
+ URLS+=("$VIDEO_URL")
81
+ if ((COUNT == 1)); then
82
+ OUTPUT="$OUTPUT_DIR/tweet-$TWEET_ID.mp4"
83
+ else
84
+ OUTPUT="$OUTPUT_DIR/tweet-$TWEET_ID-$((INDEX + 1)).mp4"
85
+ fi
86
+ [ ! -e "$OUTPUT" ] || die "output already exists: $OUTPUT"
87
+ OUTPUTS+=("$OUTPUT")
88
+ done
89
+
90
+ cleanup() {
91
+ if ((${#TEMP_FILES[@]})); then
92
+ for temporary_file in "${TEMP_FILES[@]}"; do
93
+ [ -z "$temporary_file" ] || rm -f -- "$temporary_file"
94
+ done
95
+ fi
96
+ }
97
+ trap cleanup EXIT
98
+
99
+ for ((INDEX = 0; INDEX < COUNT; INDEX++)); do
100
+ OUTPUT=${OUTPUTS[$INDEX]}
101
+ TEMP_FILE=$(mktemp "$OUTPUT.part.XXXXXX")
102
+ TEMP_FILES+=("$TEMP_FILE")
103
+
104
+ curl --proto '=https' --proto-redir '=https' \
105
+ --fail --location --silent --show-error \
106
+ --output "$TEMP_FILE" "${URLS[$INDEX]}"
107
+
108
+ MIME_TYPE=$(file --brief --mime-type "$TEMP_FILE")
109
+ [ "$MIME_TYPE" = 'video/mp4' ] ||
110
+ die "downloaded content is not video/mp4 (got $MIME_TYPE)"
111
+
112
+ if command -v ffprobe >/dev/null 2>&1; then
113
+ ffprobe -v error "$TEMP_FILE" >/dev/null ||
114
+ die "ffprobe rejected the downloaded MP4"
115
+ fi
116
+ done
117
+
118
+ # Publish files only after every download has passed validation.
119
+ for ((INDEX = 0; INDEX < COUNT; INDEX++)); do
120
+ mv -- "${TEMP_FILES[$INDEX]}" "${OUTPUTS[$INDEX]}"
121
+ TEMP_FILES[$INDEX]=''
122
+ echo "Downloaded: ${OUTPUTS[$INDEX]}"
123
+ done
124
+
125
+ trap - EXIT