@skills-store/rednote 0.1.0 → 0.1.2
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/bin/rednote.js +16 -24
- package/dist/browser/connect-browser.js +172 -0
- package/dist/browser/create-browser.js +52 -0
- package/dist/browser/index.js +35 -0
- package/dist/browser/list-browser.js +50 -0
- package/dist/browser/remove-browser.js +69 -0
- package/{scripts/index.ts → dist/index.js} +19 -25
- package/dist/rednote/checkLogin.js +139 -0
- package/dist/rednote/env.js +69 -0
- package/dist/rednote/getFeedDetail.js +268 -0
- package/dist/rednote/getProfile.js +327 -0
- package/dist/rednote/home.js +210 -0
- package/dist/rednote/index.js +130 -0
- package/dist/rednote/login.js +109 -0
- package/dist/rednote/output-format.js +116 -0
- package/dist/rednote/publish.js +376 -0
- package/dist/rednote/search.js +207 -0
- package/dist/rednote/status.js +201 -0
- package/dist/utils/browser-cli.js +155 -0
- package/dist/utils/browser-core.js +705 -0
- package/package.json +7 -4
- package/scripts/browser/connect-browser.ts +0 -218
- package/scripts/browser/create-browser.ts +0 -81
- package/scripts/browser/index.ts +0 -49
- package/scripts/browser/list-browser.ts +0 -74
- package/scripts/browser/remove-browser.ts +0 -109
- package/scripts/rednote/checkLogin.ts +0 -171
- package/scripts/rednote/env.ts +0 -79
- package/scripts/rednote/getFeedDetail.ts +0 -351
- package/scripts/rednote/getProfile.ts +0 -420
- package/scripts/rednote/home.ts +0 -316
- package/scripts/rednote/index.ts +0 -122
- package/scripts/rednote/login.ts +0 -142
- package/scripts/rednote/output-format.ts +0 -156
- package/scripts/rednote/post-types.ts +0 -51
- package/scripts/rednote/search.ts +0 -316
- package/scripts/rednote/status.ts +0 -280
- package/scripts/utils/browser-cli.ts +0 -176
- package/scripts/utils/browser-core.ts +0 -906
- package/tsconfig.json +0 -13
- /package/{scripts/rednote/collect.ts → dist/rednote/collect.js} +0 -0
- /package/{scripts/rednote/publish.ts → dist/rednote/post-types.js} +0 -0
|
@@ -1,420 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env -S node --experimental-strip-types
|
|
2
|
-
|
|
3
|
-
import * as cheerio from 'cheerio';
|
|
4
|
-
import { parseArgs } from 'node:util';
|
|
5
|
-
import vm from 'node:vm';
|
|
6
|
-
import type { Page, Response } from 'playwright-core';
|
|
7
|
-
import { printJson, runCli } from '../utils/browser-cli.ts';
|
|
8
|
-
import { resolveStatusTarget } from './status.ts';
|
|
9
|
-
import { createRednoteSession, disconnectRednoteSession, ensureRednoteLoggedIn, type RednoteSession } from './checkLogin.ts';
|
|
10
|
-
import type { RednotePost } from './post-types.ts';
|
|
11
|
-
|
|
12
|
-
export type ProfileFormat = 'json' | 'md';
|
|
13
|
-
|
|
14
|
-
export type GetProfileCliValues = {
|
|
15
|
-
instance?: string;
|
|
16
|
-
id?: string;
|
|
17
|
-
format: ProfileFormat;
|
|
18
|
-
help?: boolean;
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
export type RednoteProfileUser = {
|
|
22
|
-
userId: string | null;
|
|
23
|
-
nickname: string | null;
|
|
24
|
-
desc: string | null;
|
|
25
|
-
avatar: string | null;
|
|
26
|
-
ipLocation: string | null;
|
|
27
|
-
gender: string | null;
|
|
28
|
-
follows: string | number | null;
|
|
29
|
-
fans: string | number | null;
|
|
30
|
-
interaction: string | number | null;
|
|
31
|
-
tags: string[];
|
|
32
|
-
raw: unknown;
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
export type RednoteProfileResult = {
|
|
36
|
-
ok: true;
|
|
37
|
-
profile: {
|
|
38
|
-
userId: string;
|
|
39
|
-
url: string;
|
|
40
|
-
fetchedAt: string;
|
|
41
|
-
user: RednoteProfileUser;
|
|
42
|
-
notes: RednotePost[];
|
|
43
|
-
raw: {
|
|
44
|
-
userPageData: unknown;
|
|
45
|
-
notes: unknown;
|
|
46
|
-
};
|
|
47
|
-
};
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
type XHSProfileNoteItem = {
|
|
51
|
-
id?: string;
|
|
52
|
-
noteId?: string;
|
|
53
|
-
modelType?: string;
|
|
54
|
-
model_type?: string;
|
|
55
|
-
xsecToken?: string;
|
|
56
|
-
xsec_token?: string;
|
|
57
|
-
noteCard?: any;
|
|
58
|
-
note_card?: any;
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
function printGetProfileHelp() {
|
|
62
|
-
process.stdout.write(`rednote get-profile
|
|
63
|
-
|
|
64
|
-
Usage:
|
|
65
|
-
npx -y @skills-store/rednote get-profile [--instance NAME] --id USER_ID [--format md|json]
|
|
66
|
-
node --experimental-strip-types ./scripts/rednote/getProfile.ts --instance NAME --id USER_ID [--format md|json]
|
|
67
|
-
bun ./scripts/rednote/getProfile.ts --instance NAME --id USER_ID [--format md|json]
|
|
68
|
-
|
|
69
|
-
Options:
|
|
70
|
-
--instance NAME Optional. Defaults to the saved lastConnect instance
|
|
71
|
-
--id USER_ID Required. Xiaohongshu profile user id
|
|
72
|
-
--format FORMAT Output format: md | json. Default: md
|
|
73
|
-
-h, --help Show this help
|
|
74
|
-
`);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export function parseGetProfileCliArgs(argv: string[]): GetProfileCliValues {
|
|
78
|
-
const { values, positionals } = parseArgs({
|
|
79
|
-
args: argv,
|
|
80
|
-
allowPositionals: true,
|
|
81
|
-
strict: false,
|
|
82
|
-
options: {
|
|
83
|
-
instance: { type: 'string' },
|
|
84
|
-
id: { type: 'string' },
|
|
85
|
-
format: { type: 'string' },
|
|
86
|
-
help: { type: 'boolean', short: 'h' },
|
|
87
|
-
},
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
if (positionals.length > 0) {
|
|
91
|
-
throw new Error(`Unexpected positional arguments: ${positionals.join(' ')}`);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
const format = values.format ?? 'md';
|
|
95
|
-
if (format !== 'md' && format !== 'json') {
|
|
96
|
-
throw new Error(`Invalid --format value: ${String(format)}`);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
return {
|
|
100
|
-
instance: values.instance,
|
|
101
|
-
id: values.id,
|
|
102
|
-
format,
|
|
103
|
-
help: values.help,
|
|
104
|
-
};
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function buildProfileUrl(userId: string) {
|
|
108
|
-
const normalizedId = userId.trim();
|
|
109
|
-
if (!normalizedId) {
|
|
110
|
-
throw new Error('Profile id cannot be empty');
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
return `https://www.xiaohongshu.com/user/profile/${normalizedId}`;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
function validateProfileUrl(url: string) {
|
|
117
|
-
try {
|
|
118
|
-
const parsed = new URL(url);
|
|
119
|
-
if (!parsed.href.startsWith('https://www.xiaohongshu.com/user/profile/')) {
|
|
120
|
-
throw new Error(`url is not valid: ${url},must start with "https://www.xiaohongshu.com/user/profile/"`);
|
|
121
|
-
}
|
|
122
|
-
} catch (error) {
|
|
123
|
-
if (error instanceof TypeError) {
|
|
124
|
-
throw new Error(`url is not valid: ${url}`);
|
|
125
|
-
}
|
|
126
|
-
throw error;
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
async function getOrCreateXiaohongshuPage(session: RednoteSession) {
|
|
131
|
-
return session.page;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
function firstNonNull<T>(...values: Array<T | null | undefined>) {
|
|
135
|
-
for (const value of values) {
|
|
136
|
-
if (value !== null && value !== undefined) {
|
|
137
|
-
return value;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
return null;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
function normalizeProfileUser(userPageData: any): RednoteProfileUser {
|
|
144
|
-
const basicInfo = userPageData?.basicInfo ?? userPageData?.user ?? userPageData ?? {};
|
|
145
|
-
const interactions = userPageData?.interactions ?? userPageData?.interactionInfo ?? userPageData?.fansInfo ?? {};
|
|
146
|
-
const tags = Array.isArray(userPageData?.tags)
|
|
147
|
-
? userPageData.tags.filter((tag: any) =>tag.tagType == 'college').map((tag: any) => String(tag?.name ?? tag?.text ?? tag ?? '')).filter(Boolean)
|
|
148
|
-
: [];
|
|
149
|
-
|
|
150
|
-
const follows = interactions.find((item: any) => item?.type === 'follows')?.count;
|
|
151
|
-
const fans = interactions.find((item: any) => item?.type === 'fans')?.count;
|
|
152
|
-
const interaction = interactions.find((item: any) => item?.type === 'interaction')?.count;
|
|
153
|
-
return {
|
|
154
|
-
userId: firstNonNull(basicInfo.userId, basicInfo.user_id),
|
|
155
|
-
nickname: firstNonNull(basicInfo.nickname, basicInfo.nickName),
|
|
156
|
-
desc: firstNonNull(basicInfo.desc, basicInfo.description),
|
|
157
|
-
avatar: firstNonNull(basicInfo.image, basicInfo.avatar, basicInfo.images),
|
|
158
|
-
ipLocation: firstNonNull(basicInfo.ipLocation, basicInfo.ip_location),
|
|
159
|
-
gender: firstNonNull(basicInfo.gender, basicInfo.genderType),
|
|
160
|
-
follows: follows,
|
|
161
|
-
fans: fans,
|
|
162
|
-
interaction: interaction,
|
|
163
|
-
tags,
|
|
164
|
-
raw: userPageData,
|
|
165
|
-
};
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
function normalizeProfileNote(item: XHSProfileNoteItem): RednotePost | null {
|
|
169
|
-
const id = firstNonNull(item.id, item.noteId);
|
|
170
|
-
if (!id) {
|
|
171
|
-
return null;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
const noteCard = item.noteCard ?? item.note_card ?? {};
|
|
175
|
-
const user = noteCard.user ?? {};
|
|
176
|
-
const interactInfo = noteCard.interactInfo ?? noteCard.interact_info ?? {};
|
|
177
|
-
const cover = noteCard.cover ?? {};
|
|
178
|
-
const imageList = Array.isArray(noteCard.imageList ?? noteCard.image_list)
|
|
179
|
-
? (noteCard.imageList ?? noteCard.image_list)
|
|
180
|
-
: [];
|
|
181
|
-
const cornerTagInfo = Array.isArray(noteCard.cornerTagInfo ?? noteCard.corner_tag_info)
|
|
182
|
-
? (noteCard.cornerTagInfo ?? noteCard.corner_tag_info)
|
|
183
|
-
: [];
|
|
184
|
-
const xsecToken = firstNonNull(item.xsecToken, item.xsec_token);
|
|
185
|
-
|
|
186
|
-
return {
|
|
187
|
-
id,
|
|
188
|
-
modelType: firstNonNull(item.modelType, item.model_type) ?? 'note',
|
|
189
|
-
xsecToken,
|
|
190
|
-
url: xsecToken
|
|
191
|
-
? `https://www.xiaohongshu.com/explore/${id}?xsec_token=${xsecToken}`
|
|
192
|
-
: `https://www.xiaohongshu.com/explore/${id}`,
|
|
193
|
-
noteCard: {
|
|
194
|
-
type: firstNonNull(noteCard.type, null),
|
|
195
|
-
displayTitle: firstNonNull(noteCard.displayTitle, noteCard.display_title),
|
|
196
|
-
cover: {
|
|
197
|
-
urlDefault: firstNonNull(cover.urlDefault, cover.url_default),
|
|
198
|
-
urlPre: firstNonNull(cover.urlPre, cover.url_pre),
|
|
199
|
-
url: firstNonNull(cover.url, null),
|
|
200
|
-
fileId: firstNonNull(cover.fileId, cover.file_id),
|
|
201
|
-
width: firstNonNull(cover.width, null),
|
|
202
|
-
height: firstNonNull(cover.height, null),
|
|
203
|
-
infoList: Array.isArray(cover.infoList ?? cover.info_list)
|
|
204
|
-
? (cover.infoList ?? cover.info_list).map((info: any) => ({
|
|
205
|
-
imageScene: firstNonNull(info?.imageScene, info?.image_scene),
|
|
206
|
-
url: firstNonNull(info?.url, null),
|
|
207
|
-
}))
|
|
208
|
-
: [],
|
|
209
|
-
},
|
|
210
|
-
user: {
|
|
211
|
-
userId: firstNonNull(user.userId, user.user_id),
|
|
212
|
-
nickname: firstNonNull(user.nickname, user.nickName, user.nick_name),
|
|
213
|
-
nickName: firstNonNull(user.nickName, user.nick_name, user.nickname),
|
|
214
|
-
avatar: firstNonNull(user.avatar, null),
|
|
215
|
-
xsecToken: firstNonNull(user.xsecToken, user.xsec_token),
|
|
216
|
-
},
|
|
217
|
-
interactInfo: {
|
|
218
|
-
liked: Boolean(firstNonNull(interactInfo.liked, false)),
|
|
219
|
-
likedCount: firstNonNull(interactInfo.likedCount, interactInfo.liked_count),
|
|
220
|
-
commentCount: firstNonNull(interactInfo.commentCount, interactInfo.comment_count),
|
|
221
|
-
collectedCount: firstNonNull(interactInfo.collectedCount, interactInfo.collected_count),
|
|
222
|
-
sharedCount: firstNonNull(interactInfo.sharedCount, interactInfo.shared_count),
|
|
223
|
-
},
|
|
224
|
-
cornerTagInfo: cornerTagInfo.map((tag: any) => ({
|
|
225
|
-
type: firstNonNull(tag?.type, null),
|
|
226
|
-
text: firstNonNull(tag?.text, null),
|
|
227
|
-
})),
|
|
228
|
-
imageList: imageList.map((image: any) => ({
|
|
229
|
-
width: firstNonNull(image?.width, null),
|
|
230
|
-
height: firstNonNull(image?.height, null),
|
|
231
|
-
infoList: Array.isArray(image?.infoList ?? image?.info_list)
|
|
232
|
-
? (image.infoList ?? image.info_list).map((info: any) => ({
|
|
233
|
-
imageScene: firstNonNull(info?.imageScene, info?.image_scene),
|
|
234
|
-
url: firstNonNull(info?.url, null),
|
|
235
|
-
}))
|
|
236
|
-
: [],
|
|
237
|
-
})),
|
|
238
|
-
video: {
|
|
239
|
-
duration: firstNonNull(noteCard?.video?.capa?.duration, null),
|
|
240
|
-
},
|
|
241
|
-
},
|
|
242
|
-
};
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
function normalizeProfileNotes(notesRaw: any): RednotePost[] {
|
|
246
|
-
const candidates: XHSProfileNoteItem[] = [];
|
|
247
|
-
|
|
248
|
-
if (Array.isArray(notesRaw)) {
|
|
249
|
-
for (const entry of notesRaw) {
|
|
250
|
-
if (Array.isArray(entry)) {
|
|
251
|
-
candidates.push(...entry);
|
|
252
|
-
continue;
|
|
253
|
-
}
|
|
254
|
-
if (Array.isArray(entry?.notes)) {
|
|
255
|
-
candidates.push(...entry.notes);
|
|
256
|
-
continue;
|
|
257
|
-
}
|
|
258
|
-
if (Array.isArray(entry?.items)) {
|
|
259
|
-
candidates.push(...entry.items);
|
|
260
|
-
continue;
|
|
261
|
-
}
|
|
262
|
-
if (entry && typeof entry === 'object') {
|
|
263
|
-
candidates.push(entry);
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
return candidates.map(normalizeProfileNote).filter((item): item is RednotePost => Boolean(item));
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
function formatProfileField(value: string | number | null | undefined) {
|
|
272
|
-
return value ?? '';
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
function renderProfileMarkdown(result: RednoteProfileResult) {
|
|
276
|
-
const { user, notes, url, userId } = result.profile;
|
|
277
|
-
const lines: string[] = [];
|
|
278
|
-
|
|
279
|
-
lines.push('## UserInfo');
|
|
280
|
-
lines.push('');
|
|
281
|
-
lines.push(`- Url: ${url}`);
|
|
282
|
-
lines.push(`- Nickname: ${formatProfileField(user.nickname)}`);
|
|
283
|
-
lines.push(`- UserID: ${userId}`);
|
|
284
|
-
lines.push(`- Desc: ${formatProfileField(user.desc)}`);
|
|
285
|
-
lines.push(`- IpLocation: ${formatProfileField(user.ipLocation)}`);
|
|
286
|
-
lines.push(`- Follows: ${formatProfileField(user.follows)}`);
|
|
287
|
-
lines.push(`- Fans: ${formatProfileField(user.fans)}`);
|
|
288
|
-
lines.push(`- Interactions: ${formatProfileField(user.interaction)}`);
|
|
289
|
-
lines.push(`- Tags: ${user.tags.length > 0 ? user.tags.map((tag) => `#${tag}`).join(' ') : ''}`);
|
|
290
|
-
lines.push('');
|
|
291
|
-
lines.push('## Notes');
|
|
292
|
-
lines.push('');
|
|
293
|
-
|
|
294
|
-
if (notes.length === 0) {
|
|
295
|
-
lines.push('- Notes not found or the profile is private');
|
|
296
|
-
} else {
|
|
297
|
-
notes.forEach((note, index) => {
|
|
298
|
-
lines.push(`- Title: ${note.noteCard.displayTitle ?? ''}`);
|
|
299
|
-
lines.push(` Url: ${note.url}`);
|
|
300
|
-
lines.push(` Interaction: ${note.noteCard.interactInfo.likedCount ?? ''}`);
|
|
301
|
-
if (index < notes.length - 1) {
|
|
302
|
-
lines.push('');
|
|
303
|
-
}
|
|
304
|
-
});
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
return `${lines.join('\n')}\n`;
|
|
308
|
-
}
|
|
309
|
-
async function captureProfile(page: Page, targetUrl: string) {
|
|
310
|
-
let userPageData: any = null;
|
|
311
|
-
let notes: any = null;
|
|
312
|
-
|
|
313
|
-
const handleResponse = async (response: Response) => {
|
|
314
|
-
try {
|
|
315
|
-
const url = new URL(response.url());
|
|
316
|
-
if (response.status() !== 200 || !url.href.includes('/user/profile/')) {
|
|
317
|
-
return;
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
const html = await response.text();
|
|
321
|
-
const $ = cheerio.load(html);
|
|
322
|
-
|
|
323
|
-
$('script').each((_, element) => {
|
|
324
|
-
const scriptContent = $(element).html();
|
|
325
|
-
if (!scriptContent?.includes('window.__INITIAL_STATE__')) {
|
|
326
|
-
return;
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
const scriptText = scriptContent.substring(scriptContent.indexOf('=') + 1);
|
|
330
|
-
const sandbox: { info?: any } = {};
|
|
331
|
-
vm.createContext(sandbox);
|
|
332
|
-
vm.runInContext(`var info = ${scriptText}`, sandbox);
|
|
333
|
-
userPageData = sandbox.info?.user?.userPageData ?? userPageData;
|
|
334
|
-
|
|
335
|
-
notes = sandbox.info?.user?.notes ?? notes;
|
|
336
|
-
});
|
|
337
|
-
} catch {
|
|
338
|
-
}
|
|
339
|
-
};
|
|
340
|
-
|
|
341
|
-
page.on('response', handleResponse);
|
|
342
|
-
try {
|
|
343
|
-
await page.goto(targetUrl, { waitUntil: 'domcontentloaded' });
|
|
344
|
-
|
|
345
|
-
const deadline = Date.now() + 15_000;
|
|
346
|
-
while (Date.now() < deadline) {
|
|
347
|
-
if (userPageData || notes) {
|
|
348
|
-
break;
|
|
349
|
-
}
|
|
350
|
-
await page.waitForTimeout(200);
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
if (!userPageData && !notes) {
|
|
354
|
-
throw new Error(`Failed to capture profile detail: ${targetUrl}`);
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
return {
|
|
358
|
-
userPageData,
|
|
359
|
-
notes,
|
|
360
|
-
};
|
|
361
|
-
} finally {
|
|
362
|
-
page.off('response', handleResponse);
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
export async function getProfile(session: RednoteSession, url: string, userId: string): Promise<RednoteProfileResult> {
|
|
367
|
-
validateProfileUrl(url);
|
|
368
|
-
const page = await getOrCreateXiaohongshuPage(session);
|
|
369
|
-
const captured = await captureProfile(page, url);
|
|
370
|
-
|
|
371
|
-
return {
|
|
372
|
-
ok: true,
|
|
373
|
-
profile: {
|
|
374
|
-
userId,
|
|
375
|
-
url,
|
|
376
|
-
fetchedAt: new Date().toISOString(),
|
|
377
|
-
user: normalizeProfileUser(captured.userPageData),
|
|
378
|
-
notes: normalizeProfileNotes(captured.notes),
|
|
379
|
-
raw: captured,
|
|
380
|
-
},
|
|
381
|
-
};
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
function writeProfileOutput(result: RednoteProfileResult, format: ProfileFormat) {
|
|
385
|
-
if (format === 'json') {
|
|
386
|
-
printJson(result);
|
|
387
|
-
return;
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
process.stdout.write(renderProfileMarkdown(result));
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
export async function runGetProfileCommand(values: GetProfileCliValues = { format: 'md' }) {
|
|
394
|
-
if (values.help) {
|
|
395
|
-
printGetProfileHelp();
|
|
396
|
-
return;
|
|
397
|
-
}
|
|
398
|
-
if (!values.id) {
|
|
399
|
-
throw new Error('Missing required option: --id');
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
const target = resolveStatusTarget(values.instance);
|
|
403
|
-
const normalizedUserId = values.id.trim();
|
|
404
|
-
const session = await createRednoteSession(target);
|
|
405
|
-
|
|
406
|
-
try {
|
|
407
|
-
await ensureRednoteLoggedIn(target, 'fetching profile', session);
|
|
408
|
-
const result = await getProfile(session, buildProfileUrl(normalizedUserId), normalizedUserId);
|
|
409
|
-
writeProfileOutput(result, values.format);
|
|
410
|
-
} finally {
|
|
411
|
-
disconnectRednoteSession(session);
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
async function main() {
|
|
416
|
-
const values = parseGetProfileCliArgs(process.argv.slice(2));
|
|
417
|
-
await runGetProfileCommand(values);
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
runCli(import.meta.url, main);
|