@skills-store/rednote 0.1.13 → 0.1.14
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 +42 -28
- package/dist/rednote/env.js +1 -0
- package/dist/rednote/getFeedDetail.js +74 -30
- package/dist/rednote/getProfile.js +3 -2
- package/dist/rednote/home.js +39 -14
- package/dist/rednote/output-format.js +10 -0
- package/dist/rednote/persistence.js +578 -0
- package/dist/rednote/search.js +41 -14
- package/dist/rednote/url-format.js +41 -0
- package/dist/utils/browser-core.js +2 -0
- package/dist/utils/mouse-helper.js +105 -0
- package/package.json +7 -2
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export function decodeUrlEscapedValue(value) {
|
|
2
|
+
if (typeof value !== 'string' || value.length === 0) {
|
|
3
|
+
return null;
|
|
4
|
+
}
|
|
5
|
+
try {
|
|
6
|
+
return decodeURIComponent(value);
|
|
7
|
+
} catch {
|
|
8
|
+
return value;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
function getExploreNoteId(url) {
|
|
12
|
+
const segments = url.pathname.split('/').filter(Boolean);
|
|
13
|
+
return segments.at(-1) ?? null;
|
|
14
|
+
}
|
|
15
|
+
export function buildExploreUrl(noteId, xsecToken) {
|
|
16
|
+
const normalizedToken = decodeUrlEscapedValue(xsecToken);
|
|
17
|
+
return normalizedToken ? `https://www.xiaohongshu.com/explore/${noteId}?xsec_token=${normalizedToken}` : `https://www.xiaohongshu.com/explore/${noteId}`;
|
|
18
|
+
}
|
|
19
|
+
export function normalizeExploreUrlForOutput(url) {
|
|
20
|
+
try {
|
|
21
|
+
const parsed = new URL(url);
|
|
22
|
+
const noteId = getExploreNoteId(parsed);
|
|
23
|
+
if (!noteId) {
|
|
24
|
+
return url;
|
|
25
|
+
}
|
|
26
|
+
return buildExploreUrl(noteId, parsed.searchParams.get('xsec_token'));
|
|
27
|
+
} catch {
|
|
28
|
+
return url;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export function normalizeExploreUrlForFetch(url) {
|
|
32
|
+
try {
|
|
33
|
+
const parsed = new URL(url);
|
|
34
|
+
if (!parsed.searchParams.has('xsec_source')) {
|
|
35
|
+
parsed.searchParams.set('xsec_source', 'pc_feed');
|
|
36
|
+
}
|
|
37
|
+
return parsed.toString();
|
|
38
|
+
} catch {
|
|
39
|
+
return url;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -16,6 +16,7 @@ const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
|
16
16
|
export const PACKAGE_ROOT = path.resolve(SCRIPT_DIR, '../..');
|
|
17
17
|
export const SKILLS_ROUTER_HOME = path.join(os.homedir(), '.skills-router');
|
|
18
18
|
export const REDNOTE_STORAGE_ROOT = path.join(SKILLS_ROUTER_HOME, 'rednote');
|
|
19
|
+
export const REDNOTE_DATABASE_PATH = path.join(REDNOTE_STORAGE_ROOT, 'main.db');
|
|
19
20
|
export const INSTANCES_DIR = path.join(REDNOTE_STORAGE_ROOT, 'instances');
|
|
20
21
|
export const INSTANCE_STORE_PATH = path.join(INSTANCES_DIR, 'data.json');
|
|
21
22
|
export const LEGACY_PACKAGE_INSTANCES_DIR = path.join(PACKAGE_ROOT, 'instances');
|
|
@@ -100,6 +101,7 @@ export function getRednoteEnvironmentInfo() {
|
|
|
100
101
|
nodeVersion: process.version,
|
|
101
102
|
storageHome: SKILLS_ROUTER_HOME,
|
|
102
103
|
storageRoot: REDNOTE_STORAGE_ROOT,
|
|
104
|
+
databasePath: REDNOTE_DATABASE_PATH,
|
|
103
105
|
instancesDir: INSTANCES_DIR,
|
|
104
106
|
instanceStorePath: INSTANCE_STORE_PATH,
|
|
105
107
|
legacyPackageInstancesDir: LEGACY_PACKAGE_INSTANCES_DIR
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { createCursor, getRandomPagePoint } from 'playwright-ghost-cursor';
|
|
2
|
+
const ghostCursorCache = new WeakMap();
|
|
3
|
+
function randomInt(min, max) {
|
|
4
|
+
const lower = Math.min(min, max);
|
|
5
|
+
const upper = Math.max(min, max);
|
|
6
|
+
return Math.floor(Math.random() * (upper - lower + 1)) + lower;
|
|
7
|
+
}
|
|
8
|
+
function getCursor(page) {
|
|
9
|
+
const cached = ghostCursorCache.get(page);
|
|
10
|
+
if (cached) {
|
|
11
|
+
return cached;
|
|
12
|
+
}
|
|
13
|
+
const cursor = createCursor(page);
|
|
14
|
+
ghostCursorCache.set(page, cursor);
|
|
15
|
+
return cursor;
|
|
16
|
+
}
|
|
17
|
+
async function resolveRandomPoint(page) {
|
|
18
|
+
const point = await getRandomPagePoint(page).catch(()=>null);
|
|
19
|
+
if (point) {
|
|
20
|
+
return point;
|
|
21
|
+
}
|
|
22
|
+
const viewport = page.viewportSize() ?? {
|
|
23
|
+
width: 1280,
|
|
24
|
+
height: 720
|
|
25
|
+
};
|
|
26
|
+
return {
|
|
27
|
+
x: Math.round(viewport.width * 0.5),
|
|
28
|
+
y: Math.round(viewport.height * 0.35)
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
export async function simulateMouseMove(page, options = {}) {
|
|
32
|
+
const cursor = getCursor(page);
|
|
33
|
+
const settleMs = options.settleMs ?? 120;
|
|
34
|
+
const moveSpeed = options.moveSpeed ?? randomInt(6, 14);
|
|
35
|
+
if (options.locator) {
|
|
36
|
+
const handle = await options.locator.elementHandle().catch(()=>null);
|
|
37
|
+
if (handle) {
|
|
38
|
+
await cursor.move(handle, {
|
|
39
|
+
moveSpeed,
|
|
40
|
+
moveDelay: 0,
|
|
41
|
+
paddingPercentage: 70
|
|
42
|
+
});
|
|
43
|
+
if (settleMs > 0) {
|
|
44
|
+
await page.waitForTimeout(settleMs);
|
|
45
|
+
}
|
|
46
|
+
return cursor.getLocation();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
const point = await resolveRandomPoint(page);
|
|
50
|
+
await cursor.moveTo(point, {
|
|
51
|
+
moveSpeed,
|
|
52
|
+
moveDelay: 0
|
|
53
|
+
});
|
|
54
|
+
if (settleMs > 0) {
|
|
55
|
+
await page.waitForTimeout(settleMs);
|
|
56
|
+
}
|
|
57
|
+
return point;
|
|
58
|
+
}
|
|
59
|
+
export async function simulateMouseWheel(page, options = {}) {
|
|
60
|
+
if (options.moveBeforeScroll !== false) {
|
|
61
|
+
await simulateMouseMove(page, options);
|
|
62
|
+
}
|
|
63
|
+
const repeats = Math.max(options.repeats ?? 1, 1);
|
|
64
|
+
const deltaY = options.deltaY ?? 360;
|
|
65
|
+
const stepPauseMs = options.stepPauseMs ?? 180;
|
|
66
|
+
const settleMs = options.settleMs ?? 150;
|
|
67
|
+
for(let index = 0; index < repeats; index += 1){
|
|
68
|
+
await page.mouse.wheel(0, deltaY);
|
|
69
|
+
const pause = index === repeats - 1 ? settleMs : stepPauseMs;
|
|
70
|
+
if (pause > 0) {
|
|
71
|
+
await page.waitForTimeout(pause);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
export async function simulateMousePresence(page, options = {}) {
|
|
76
|
+
const cursor = getCursor(page);
|
|
77
|
+
const duration = randomInt(options.minDurationMs ?? 3_000, options.maxDurationMs ?? 5_000);
|
|
78
|
+
const deadline = Date.now() + duration;
|
|
79
|
+
let movedToLocator = false;
|
|
80
|
+
while(Date.now() < deadline){
|
|
81
|
+
if (options.locator && !movedToLocator) {
|
|
82
|
+
await simulateMouseMove(page, {
|
|
83
|
+
locator: options.locator,
|
|
84
|
+
settleMs: randomInt(80, 220),
|
|
85
|
+
moveSpeed: randomInt(7, 15)
|
|
86
|
+
}).catch(()=>{});
|
|
87
|
+
movedToLocator = true;
|
|
88
|
+
} else {
|
|
89
|
+
const point = await resolveRandomPoint(page);
|
|
90
|
+
await cursor.moveTo(point, {
|
|
91
|
+
moveSpeed: randomInt(5, 14),
|
|
92
|
+
moveDelay: randomInt(0, 120),
|
|
93
|
+
randomizeMoveDelay: true
|
|
94
|
+
}).catch(()=>{});
|
|
95
|
+
}
|
|
96
|
+
if (options.allowScroll && Math.random() < 0.2) {
|
|
97
|
+
await page.mouse.wheel(0, randomInt(-160, 220)).catch(()=>{});
|
|
98
|
+
}
|
|
99
|
+
const remaining = deadline - Date.now();
|
|
100
|
+
if (remaining <= 0) {
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
await page.waitForTimeout(Math.min(randomInt(180, 650), remaining));
|
|
104
|
+
}
|
|
105
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@skills-store/rednote",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.14",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -35,12 +35,17 @@
|
|
|
35
35
|
"bun:get-profile": "bun ./scripts/rednote/getProfile.ts"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
+
"@libsql/client": "^0.17.0",
|
|
39
|
+
"better-sqlite3": "^12.8.0",
|
|
38
40
|
"cheerio": "^1.2.0",
|
|
39
41
|
"chrome-paths": "^1.0.1",
|
|
40
42
|
"edge-paths": "^3.0.5",
|
|
43
|
+
"nanoid": "^5.1.7",
|
|
41
44
|
"pid-port": "^1.0.2",
|
|
42
45
|
"playwright-core": "^1.52.0",
|
|
43
|
-
"
|
|
46
|
+
"playwright-ghost-cursor": "^1.0.2",
|
|
47
|
+
"ps-list": "^8.1.1",
|
|
48
|
+
"typeorm": "^0.3.28"
|
|
44
49
|
},
|
|
45
50
|
"engines": {
|
|
46
51
|
"node": ">=22.6.0",
|