@skills-store/rednote 0.1.14 → 0.1.15

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.
package/README.md CHANGED
@@ -36,10 +36,10 @@ For most tasks, run commands in this order:
36
36
  rednote env
37
37
  rednote browser create --name seller-main --browser chrome --port 9222
38
38
  rednote browser connect --instance seller-main
39
- rednote login --instance seller-main
40
- rednote status --instance seller-main
41
- rednote search --instance seller-main --keyword 护肤
42
- rednote interact --instance seller-main --url "https://www.xiaohongshu.com/explore/xxx?xsec_token=yyy" --like --collect --comment "写得真好"
39
+ rednote login
40
+ rednote status
41
+ rednote search --keyword 护肤
42
+ rednote interact --url "https://www.xiaohongshu.com/explore/xxx?xsec_token=yyy" --like --collect --comment "写得真好"
43
43
  ```
44
44
 
45
45
  ## Commands
@@ -68,7 +68,7 @@ Use `env` first when checking installation, runtime info, or storage paths.
68
68
  ### `status`
69
69
 
70
70
  ```bash
71
- rednote status --instance seller-main
71
+ rednote status
72
72
  ```
73
73
 
74
74
  Use `status` to confirm whether an instance exists, is running, and appears logged in.
@@ -76,7 +76,7 @@ Use `status` to confirm whether an instance exists, is running, and appears logg
76
76
  ### `check-login`
77
77
 
78
78
  ```bash
79
- rednote check-login --instance seller-main
79
+ rednote check-login
80
80
  ```
81
81
 
82
82
  Use `check-login` when you only want to verify whether the session is still valid.
@@ -84,7 +84,7 @@ Use `check-login` when you only want to verify whether the session is still vali
84
84
  ### `login`
85
85
 
86
86
  ```bash
87
- rednote login --instance seller-main
87
+ rednote login
88
88
  ```
89
89
 
90
90
  Use `login` after `browser connect` if the instance is not authenticated yet.
@@ -92,7 +92,7 @@ Use `login` after `browser connect` if the instance is not authenticated yet.
92
92
  ### `home`
93
93
 
94
94
  ```bash
95
- rednote home --instance seller-main --format md --save
95
+ rednote home --format md --save
96
96
  ```
97
97
 
98
98
  Use `home` when you want the current home feed and optionally want to save it to disk.
@@ -103,6 +103,10 @@ The terminal output always uses the compact summary format below, even when `--f
103
103
  id=<database nanoid>
104
104
  title=<post title>
105
105
  like=<liked count>
106
+
107
+ id=...
108
+ title=...
109
+ like=...
106
110
  ```
107
111
 
108
112
  Captured home feed posts are upserted into `~/.skills-router/rednote/main.db` (the same path returned by `rednote env`). They are stored in the `rednote_posts` table, and the printed `id` is that table's `nanoid(16)` primary key.
@@ -110,8 +114,8 @@ Captured home feed posts are upserted into `~/.skills-router/rednote/main.db` (t
110
114
  ### `search`
111
115
 
112
116
  ```bash
113
- rednote search --instance seller-main --keyword 护肤
114
- rednote search --instance seller-main --keyword 护肤 --format json --save ./output/search.jsonl
117
+ rednote search --keyword 护肤
118
+ rednote search --keyword 护肤 --format json --save ./output/search.jsonl
115
119
  ```
116
120
 
117
121
  Use `search` for keyword-based note lookup.
@@ -129,8 +133,8 @@ Captured search results are also upserted into `~/.skills-router/rednote/main.db
129
133
  ### `get-feed-detail`
130
134
 
131
135
  ```bash
132
- rednote get-feed-detail --instance seller-main --url "https://www.xiaohongshu.com/explore/xxx?xsec_token=yyy"
133
- rednote get-feed-detail --instance seller-main --id AynQ7_utnNiW1Ytk
136
+ rednote get-feed-detail --id <nanoid>
137
+ rednote get-feed-detail --url "https://www.xiaohongshu.com/explore/xxx?xsec_token=yyy"
134
138
  ```
135
139
 
136
140
  Use `get-feed-detail` when you already have a Xiaohongshu note URL, or when you have a database `id` returned by `home` or `search`. With `--id`, the CLI looks up the saved URL from `~/.skills-router/rednote/main.db` and then navigates with that raw URL.
@@ -140,7 +144,7 @@ Captured note details and comments are also upserted into `~/.skills-router/redn
140
144
  ### `get-profile`
141
145
 
142
146
  ```bash
143
- rednote get-profile --instance seller-main --id USER_ID
147
+ rednote get-profile --id USER_ID
144
148
  ```
145
149
 
146
150
  Use `get-profile` when you want author or account profile information.
@@ -149,8 +153,8 @@ Use `get-profile` when you want author or account profile information.
149
153
  ### `interact`
150
154
 
151
155
  ```bash
152
- rednote interact --instance seller-main --url "https://www.xiaohongshu.com/explore/xxx?xsec_token=yyy" --like --collect
153
- rednote interact --instance seller-main --url "https://www.xiaohongshu.com/explore/xxx?xsec_token=yyy" --like --collect --comment "写得真好"
156
+ rednote interact --id <nanoid> --like --collect
157
+ rednote interact --id <nanoid> --like --collect --comment "写得真好"
154
158
  ```
155
159
 
156
160
  Use `interact` when you want the single entrypoint for note operations such as like, collect, and comment in one command. Use `--comment TEXT` for replies; there is no standalone `comment` command.
@@ -168,318 +172,6 @@ Use `interact` when you want the single entrypoint for note operations such as l
168
172
  - `--url` is required for `interact`; at least one of `--like`, `--collect`, or `--comment TEXT` must be provided.
169
173
  - replies are sent with `interact --comment TEXT`.
170
174
 
171
- ## JSON success shapes
172
-
173
- Use these shapes as the success model when a command returns JSON.
174
-
175
- ### Shared note item
176
-
177
- `home`, `search`, and `profile.notes` share the normalized `RednotePost` shape:
178
-
179
- ```json
180
- {
181
- "id": "string",
182
- "modelType": "string",
183
- "xsecToken": "string|null",
184
- "url": "string",
185
- "noteCard": {
186
- "type": "string|null",
187
- "displayTitle": "string|null",
188
- "cover": {
189
- "urlDefault": "string|null",
190
- "urlPre": "string|null",
191
- "url": "string|null",
192
- "fileId": "string|null",
193
- "width": "number|null",
194
- "height": "number|null",
195
- "infoList": [{ "imageScene": "string|null", "url": "string|null" }]
196
- },
197
- "user": {
198
- "userId": "string|null",
199
- "nickname": "string|null",
200
- "nickName": "string|null",
201
- "avatar": "string|null",
202
- "xsecToken": "string|null"
203
- },
204
- "interactInfo": {
205
- "liked": "boolean",
206
- "likedCount": "string|null",
207
- "commentCount": "string|null",
208
- "collectedCount": "string|null",
209
- "sharedCount": "string|null"
210
- },
211
- "cornerTagInfo": [{ "type": "string|null", "text": "string|null" }],
212
- "imageList": [{ "width": "number|null", "height": "number|null", "infoList": [{ "imageScene": "string|null", "url": "string|null" }] }],
213
- "video": { "duration": "number|null" }
214
- }
215
- }
216
- ```
217
-
218
- ### `env --format json`
219
-
220
- `env` is the main exception: it returns a raw environment object instead of `{ "ok": true, ... }`.
221
-
222
- ```json
223
- {
224
- "packageRoot": "string",
225
- "homeDir": "string",
226
- "platform": "string",
227
- "nodeVersion": "string",
228
- "storageHome": "string",
229
- "storageRoot": "string",
230
- "databasePath": "string",
231
- "instancesDir": "string",
232
- "instanceStorePath": "string",
233
- "legacyPackageInstancesDir": "string"
234
- }
235
- ```
236
-
237
- ### Browser commands
238
-
239
- `browser list`:
240
-
241
- ```json
242
- {
243
- "lastConnect": { "scope": "default|custom", "name": "string", "browser": "chrome|edge|chromium|brave" } | null,
244
- "instances": [{
245
- "type": "chrome|edge|chromium|brave",
246
- "name": "string",
247
- "executablePath": "string",
248
- "userDataDir": "string",
249
- "exists": true,
250
- "inUse": false,
251
- "pid": "number|null",
252
- "lockFiles": ["string"],
253
- "matchedProcess": { "pid": "number", "name": "string", "cmdline": "string" } | null,
254
- "staleLock": false,
255
- "remotePort": "number|null",
256
- "scope": "default|custom",
257
- "instanceName": "string",
258
- "createdAt": "string|null",
259
- "lastConnect": false
260
- }]
261
- }
262
- ```
263
-
264
- `browser create`:
265
-
266
- ```json
267
- {
268
- "ok": true,
269
- "instance": {
270
- "name": "string",
271
- "browser": "chrome|edge|chromium|brave",
272
- "userDataDir": "string",
273
- "createdAt": "string",
274
- "remoteDebuggingPort": "number|undefined"
275
- }
276
- }
277
- ```
278
-
279
- `browser connect`:
280
-
281
- ```json
282
- {
283
- "ok": true,
284
- "type": "chrome|edge|chromium|brave",
285
- "executablePath": "string",
286
- "userDataDir": "string",
287
- "remoteDebuggingPort": "number",
288
- "wsUrl": "string",
289
- "pid": "number|null"
290
- }
291
- ```
292
-
293
- `browser remove`:
294
-
295
- ```json
296
- {
297
- "ok": true,
298
- "removedInstance": {
299
- "name": "string",
300
- "browser": "chrome|edge|chromium|brave",
301
- "userDataDir": "string",
302
- "createdAt": "string",
303
- "remoteDebuggingPort": "number|undefined"
304
- },
305
- "removedDir": true,
306
- "closedPids": ["number"]
307
- }
308
- ```
309
-
310
- ### Session and account commands
311
-
312
- `status`:
313
-
314
- ```json
315
- {
316
- "ok": true,
317
- "instance": {
318
- "scope": "default|custom",
319
- "name": "string",
320
- "browser": "chrome|edge|chromium|brave",
321
- "source": "argument|last-connect|single-instance",
322
- "status": "running|stopped|missing|stale-lock",
323
- "exists": true,
324
- "inUse": false,
325
- "pid": "number|null",
326
- "remotePort": "number|null",
327
- "userDataDir": "string",
328
- "createdAt": "string|null",
329
- "lastConnect": false
330
- },
331
- "rednote": {
332
- "loginStatus": "logged-in|logged-out|unknown",
333
- "lastLoginAt": "string|null"
334
- }
335
- }
336
- ```
337
-
338
- `check-login`:
339
-
340
- ```json
341
- {
342
- "ok": true,
343
- "rednote": {
344
- "loginStatus": "logged-in|logged-out|unknown",
345
- "lastLoginAt": "string|null",
346
- "needLogin": false,
347
- "checkedAt": "string"
348
- }
349
- }
350
- ```
351
-
352
- `login`:
353
-
354
- ```json
355
- {
356
- "ok": true,
357
- "rednote": {
358
- "loginClicked": true,
359
- "pageUrl": "string",
360
- "waitingForPhoneLogin": true,
361
- "qrCodePath": "string|null",
362
- "message": "string"
363
- }
364
- }
365
- ```
366
-
367
- ### Feed and profile commands
368
-
369
- `home` stdout (both `md` and `json`):
370
-
371
- ```text
372
- id=<database nanoid>
373
- title=<post title>
374
- like=<liked count>
375
- ```
376
-
377
- `home --format json --save PATH` writes the raw `RednotePost[]` array to disk, while stdout still prints the summary list above.
378
-
379
- `search` stdout (both `md` and `json`):
380
-
381
- ```text
382
- id=<database nanoid>
383
- title=<post title>
384
- like=<liked count>
385
- ```
386
-
387
- `search --format json --save PATH` writes the raw `RednotePost[]` array to disk, while stdout still prints the summary list above.
388
-
389
- `get-feed-detail --format json`:
390
-
391
- ```json
392
- {
393
- "ok": true,
394
- "detail": {
395
- "fetchedAt": "string",
396
- "total": "number",
397
- "items": [{
398
- "url": "string",
399
- "note": {
400
- "noteId": "string|null",
401
- "title": "string|null",
402
- "desc": "string|null",
403
- "type": "string|null",
404
- "interactInfo": {
405
- "liked": "boolean|null",
406
- "likedCount": "string|null",
407
- "commentCount": "string|null",
408
- "collected": "boolean|null",
409
- "collectedCount": "string|null",
410
- "shareCount": "string|null",
411
- "followed": "boolean|null"
412
- },
413
- "tagList": [{ "name": "string|null" }],
414
- "imageList": [{ "urlDefault": "string|null", "urlPre": "string|null", "width": "number|null", "height": "number|null" }],
415
- "video": { "url": "string|null", "raw": "unknown" } | null,
416
- "raw": "unknown"
417
- },
418
- "comments": [{
419
- "id": "string|null",
420
- "content": "string|null",
421
- "userId": "string|null",
422
- "nickname": "string|null",
423
- "likedCount": "string|null",
424
- "subCommentCount": "number|null",
425
- "raw": "unknown"
426
- }]
427
- }]
428
- }
429
- }
430
- ```
431
-
432
- `get-profile --format json`:
433
-
434
- ```json
435
- {
436
- "ok": true,
437
- "profile": {
438
- "userId": "string",
439
- "url": "string",
440
- "fetchedAt": "string",
441
- "user": {
442
- "userId": "string|null",
443
- "nickname": "string|null",
444
- "desc": "string|null",
445
- "avatar": "string|null",
446
- "ipLocation": "string|null",
447
- "gender": "string|null",
448
- "follows": "string|number|null",
449
- "fans": "string|number|null",
450
- "interaction": "string|number|null",
451
- "tags": ["string"],
452
- "raw": "unknown"
453
- },
454
- "notes": ["RednotePost"],
455
- "raw": {
456
- "userPageData": "unknown",
457
- "notes": "unknown"
458
- }
459
- }
460
- }
461
- ```
462
-
463
- ### Action commands
464
-
465
- `publish`:
466
-
467
- ```json
468
- {
469
- "ok": true,
470
- "message": "string"
471
- }
472
- ```
473
-
474
- `interact`:
475
-
476
- ```json
477
- {
478
- "ok": true,
479
- "message": "string"
480
- }
481
- ```
482
-
483
175
  ## Storage
484
176
 
485
177
  The CLI stores browser instances and metadata under:
package/dist/index.js CHANGED
@@ -20,7 +20,7 @@ Commands:
20
20
  check-login [--instance NAME]
21
21
  login [--instance NAME]
22
22
  publish [--instance NAME]
23
- interact [--instance NAME] --url URL [--like] [--collect] [--comment TEXT]
23
+ interact [--instance NAME] [--id ID | --url URL] [--like] [--collect] [--comment TEXT]
24
24
  home [--instance NAME] [--format md|json] [--save [PATH]]
25
25
  search [--instance NAME] --keyword TEXT [--format md|json] [--save [PATH]]
26
26
  get-feed-detail [--instance NAME] --url URL [--format md|json]
@@ -32,7 +32,7 @@ Examples:
32
32
  npx -y @skills-store/rednote browser connect --instance seller-main
33
33
  npx -y @skills-store/rednote env
34
34
  npx -y @skills-store/rednote publish --instance seller-main --type video --video ./note.mp4 --title 标题 --content 描述
35
- npx -y @skills-store/rednote interact --instance seller-main --url "https://www.xiaohongshu.com/explore/xxx?xsec_token=yyy" --like --collect --comment "写得真好"
35
+ npx -y @skills-store/rednote interact --instance seller-main --id NOTE_ID --like --collect --comment "写得真好"
36
36
  npx -y @skills-store/rednote search --instance seller-main --keyword 护肤
37
37
  `);
38
38
  }
@@ -11,7 +11,7 @@ Commands:
11
11
  check-login [--instance NAME]
12
12
  login [--instance NAME]
13
13
  publish [--instance NAME]
14
- interact [--instance NAME] --url URL [--like] [--collect] [--comment TEXT]
14
+ interact [--instance NAME] [--id ID | --url URL] [--like] [--collect] [--comment TEXT]
15
15
  home [--instance NAME] [--format md|json] [--save [PATH]]
16
16
  search [--instance NAME] --keyword TEXT [--format md|json] [--save [PATH]]
17
17
  get-feed-detail [--instance NAME] --url URL [--url URL] [--comments [COUNT]] [--format md|json] [--save PATH]
@@ -25,7 +25,7 @@ Examples:
25
25
  npx -y @skills-store/rednote status --instance seller-main
26
26
  npx -y @skills-store/rednote login --instance seller-main
27
27
  npx -y @skills-store/rednote publish --instance seller-main --type video --video ./note.mp4 --title "Video title" --content "Video description"
28
- npx -y @skills-store/rednote interact --instance seller-main --url "https://www.xiaohongshu.com/explore/xxx?xsec_token=yyy" --like --collect --comment "Great post"
28
+ npx -y @skills-store/rednote interact --instance seller-main --id NOTE_ID --like --collect --comment "Great post"
29
29
  npx -y @skills-store/rednote home --instance seller-main --format md --save
30
30
  npx -y @skills-store/rednote search --instance seller-main --keyword skincare --format json --save ./output/search.json
31
31
  npx -y @skills-store/rednote get-feed-detail --instance seller-main --url "https://www.xiaohongshu.com/explore/xxx?xsec_token=yyy" --comments 100 --format json --save ./output/feed-detail.json
@@ -4,6 +4,7 @@ import { printJson, runCli } from '../utils/browser-cli.js';
4
4
  import { resolveStatusTarget } from './status.js';
5
5
  import { createRednoteSession, disconnectRednoteSession, ensureRednoteLoggedIn } from './checkLogin.js';
6
6
  import { getFeedDetails } from './getFeedDetail.js';
7
+ import { findPersistedPostUrlByRecordId, initializeRednoteDatabase } from './persistence.js';
7
8
  const INTERACT_CONTAINER_SELECTOR = '.interact-container';
8
9
  const LIKE_WRAPPER_SELECTOR = `${INTERACT_CONTAINER_SELECTOR} .like-wrapper`;
9
10
  const COLLECT_WRAPPER_SELECTOR = `${INTERACT_CONTAINER_SELECTOR} .collect-wrapper, ${INTERACT_CONTAINER_SELECTOR} #note-page-collect-board-guide`;
@@ -14,13 +15,14 @@ function printInteractHelp() {
14
15
  process.stdout.write(`rednote interact
15
16
 
16
17
  Usage:
17
- npx -y @skills-store/rednote interact [--instance NAME] --url URL [--like] [--collect] [--comment TEXT]
18
- node --experimental-strip-types ./scripts/rednote/interact.ts --instance NAME --url URL [--like] [--collect] [--comment TEXT]
19
- bun ./scripts/rednote/interact.ts --instance NAME --url URL [--like] [--collect] [--comment TEXT]
18
+ npx -y @skills-store/rednote interact [--instance NAME] [--id ID | --url URL] [--like] [--collect] [--comment TEXT]
19
+ node --experimental-strip-types ./scripts/rednote/interact.ts [--instance NAME] [--id ID | --url URL] [--like] [--collect] [--comment TEXT]
20
+ bun ./scripts/rednote/interact.ts [--instance NAME] [--id ID | --url URL] [--like] [--collect] [--comment TEXT]
20
21
 
21
22
  Options:
22
23
  --instance NAME Optional. Defaults to the saved lastConnect instance
23
- --url URL Required. Xiaohongshu explore url
24
+ --id ID Optional. Database record id from home/search output
25
+ --url URL Optional. Xiaohongshu explore url
24
26
  --like Optional. Perform like
25
27
  --collect Optional. Perform collect
26
28
  --comment TEXT Optional. Post comment content
@@ -36,6 +38,9 @@ export function parseInteractCliArgs(argv) {
36
38
  instance: {
37
39
  type: 'string'
38
40
  },
41
+ id: {
42
+ type: 'string'
43
+ },
39
44
  url: {
40
45
  type: 'string'
41
46
  },
@@ -59,6 +64,7 @@ export function parseInteractCliArgs(argv) {
59
64
  }
60
65
  return {
61
66
  instance: values.instance,
67
+ id: values.id,
62
68
  url: values.url,
63
69
  like: values.like,
64
70
  collect: values.collect,
@@ -253,8 +259,8 @@ export async function interactWithFeed(session, url, actions, commentContent) {
253
259
  }
254
260
  const page = await getOrCreateXiaohongshuPage(session);
255
261
  await waitForInteractContainer(page);
256
- let liked = detailItem.note.interactInfo.liked === true;
257
- let collected = detailItem.note.interactInfo.collected === true;
262
+ let liked = detailItem.note.liked === true;
263
+ let collected = detailItem.note.collected === true;
258
264
  const messages = [];
259
265
  for (const action of actions){
260
266
  if (action === 'like') {
@@ -280,17 +286,34 @@ export async function interactWithFeed(session, url, actions, commentContent) {
280
286
  message: `${messages.join('; ')}: ${url}`
281
287
  };
282
288
  }
289
+ async function resolveInteractUrl(values, instanceName) {
290
+ if (values.id) {
291
+ if (!instanceName) {
292
+ throw new Error('The --id option requires an instance-backed session.');
293
+ }
294
+ const url = await findPersistedPostUrlByRecordId(instanceName, ensureNonEmpty(values.id, '--id'));
295
+ if (!url) {
296
+ throw new Error(`No saved post url found for id: ${values.id}`);
297
+ }
298
+ return url;
299
+ }
300
+ if (values.url) {
301
+ return ensureNonEmpty(values.url, '--url');
302
+ }
303
+ throw new Error('Missing required option: --id or --url');
304
+ }
283
305
  export async function runInteractCommand(values = {}) {
284
306
  if (values.help) {
285
307
  printInteractHelp();
286
308
  return;
287
309
  }
288
- const url = ensureNonEmpty(values.url, '--url');
289
310
  const { actions, commentContent } = resolveInteractActions(values);
311
+ await initializeRednoteDatabase();
290
312
  const target = resolveStatusTarget(values.instance);
291
313
  const session = await createRednoteSession(target);
292
314
  try {
293
315
  await ensureRednoteLoggedIn(target, `performing ${actions.join(', ')} interact`, session);
316
+ const url = await resolveInteractUrl(values, target.instanceName);
294
317
  const result = await interactWithFeed(session, url, actions, commentContent);
295
318
  printJson(result);
296
319
  } finally{
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skills-store/rednote",
3
- "version": "0.1.14",
3
+ "version": "0.1.15",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "bin": {