chore: import upstream snapshot with attribution
E2E Headed Chrome / e2e-headed (macos-15) (push) Has been cancelled
E2E Headed Chrome / e2e-headed (ubuntu-latest) (push) Has been cancelled
E2E Headed Chrome / e2e-headed (windows-latest) (push) Has been cancelled
CI / build (macos-latest) (push) Has been cancelled
CI / build (ubuntu-latest) (push) Has been cancelled
CI / build (windows-latest) (push) Has been cancelled
CI / unit-test (push) Has been cancelled
CI / bun-test (push) Has been cancelled
CI / adapter-test (push) Has been cancelled
CI / smoke-test (macos-latest) (push) Has been cancelled
CI / smoke-test (ubuntu-latest) (push) Has been cancelled
Security Audit / audit (push) Has been cancelled
Build Chrome Extension / build (push) Has been cancelled
Trigger Website Rebuild (Docs Updated) / dispatch (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 12:39:48 +08:00
commit 9b395f5cc3
2400 changed files with 376116 additions and 0 deletions
+65
View File
@@ -0,0 +1,65 @@
import { AuthRequiredError, CommandExecutionError } from '@jackwener/opencli/errors';
import { registerSiteAuthCommands } from '../_shared/site-auth.js';
// Nowcoder's logged-in token cookie is `t`. The numeric uid is not in any
// readable cookie (NOWCODERUID is a device hash), so identity is resolved from
// the server-rendered nav avatar link `/users/<uid>` (first /users/ link in
// DOM order, ahead of body feed recommendations) and confirmed through the
// gateway profile API.
async function hasNowcoderSessionCookie(page) {
const cookies = await page.getCookies({ url: 'https://www.nowcoder.com' });
return cookies.some(cookie => cookie.name === 't' && cookie.value);
}
const WHOAMI_PROBE = `(async () => {
try {
const link = document.querySelector('a[href*="/users/"]');
const href = link ? (link.getAttribute('href') || '') : '';
const match = href.match(/\\/users\\/(\\d+)/);
if (!match) {
return { kind: 'auth', detail: 'No logged-in nowcoder profile link in nav (anonymous)' };
}
const uid = match[1];
const r = await fetch('https://gw-c.nowcoder.com/api/sparta/user/profile/' + uid, {
credentials: 'include',
headers: { Accept: 'application/json' },
});
if (r.status === 401 || r.status === 403) return { kind: 'auth', detail: 'nowcoder profile HTTP ' + r.status };
if (!r.ok) return { kind: 'http', httpStatus: r.status };
const d = await r.json();
if (!d || !d.success || !d.data || !d.data.id) {
return { kind: 'auth', detail: 'nowcoder profile returned no user data (anonymous)' };
}
return { ok: true, user_id: String(d.data.id), nickname: String(d.data.nickname || '') };
} catch (e) {
return { kind: 'exception', detail: String(e && e.message || e) };
}
})()`;
async function verifyNowcoderIdentity(page) {
if (!await hasNowcoderSessionCookie(page)) {
throw new AuthRequiredError('nowcoder.com', 'Nowcoder t cookie missing (anonymous)');
}
await page.goto('https://www.nowcoder.com/');
await page.wait(2);
const probe = await page.evaluate(WHOAMI_PROBE);
if (probe?.kind === 'auth') throw new AuthRequiredError('nowcoder.com', probe.detail);
if (probe?.kind === 'http') throw new CommandExecutionError(`HTTP ${probe.httpStatus} from nowcoder profile API`);
if (probe?.kind === 'exception') throw new CommandExecutionError(`Nowcoder whoami failed: ${probe.detail}`);
if (!probe?.ok) throw new CommandExecutionError(`Unexpected nowcoder probe: ${JSON.stringify(probe)}`);
return { user_id: probe.user_id, nickname: probe.nickname };
}
registerSiteAuthCommands({
site: 'nowcoder',
domain: 'nowcoder.com',
loginUrl: 'https://www.nowcoder.com/login',
columns: ['user_id', 'nickname'],
verify: verifyNowcoderIdentity,
poll: async (page) => {
if (!await hasNowcoderSessionCookie(page)) {
throw new AuthRequiredError('nowcoder.com', 'Waiting for Nowcoder login');
}
return verifyNowcoderIdentity(page);
},
});
+24
View File
@@ -0,0 +1,24 @@
import { cli, Strategy } from '@jackwener/opencli/registry';
cli({
site: 'nowcoder',
name: 'companies',
access: 'read',
description: 'Hot companies for interview prep',
domain: 'www.nowcoder.com',
strategy: Strategy.PUBLIC,
browser: false,
args: [
{ name: 'job', type: 'str', default: '11002', help: 'Job ID (11002=Java, 11003=C++, 11200=Backend, 11203=QA, 11201=Frontend)' },
],
columns: ['rank', 'company', 'companyId'],
pipeline: [
{ fetch: { url: 'https://gw-c.nowcoder.com/api/sparta/company-question/hot-company-list?jobId=${{ args.job }}' } },
{ select: 'data.result' },
{ map: {
rank: '${{ index + 1 }}',
company: '${{ item.companyName }}',
companyId: '${{ item.companyId }}',
} },
],
});
+28
View File
@@ -0,0 +1,28 @@
import { cli, Strategy } from '@jackwener/opencli/registry';
cli({
site: 'nowcoder',
name: 'creators',
access: 'read',
description: 'Top content creators leaderboard',
domain: 'www.nowcoder.com',
strategy: Strategy.PUBLIC,
browser: false,
args: [
{ name: 'limit', type: 'int', default: 10, help: 'Number of items' },
],
columns: ['rank', 'nickname', 'school', 'level', 'heat', 'tag'],
pipeline: [
{ fetch: { url: 'https://gw-c.nowcoder.com/api/sparta/content/creator/top-list' } },
{ select: 'data.result' },
{ map: {
rank: '${{ index + 1 }}',
nickname: `\${{ item.userBrief?.nickname || '' }}`,
school: `\${{ item.userBrief?.educationInfo || '' }}`,
level: `\${{ item.userBrief?.honorLevelName || '' }}`,
heat: '${{ item.hotValue }}',
tag: `\${{ item.tag || '' }}`,
} },
{ limit: '${{ args.limit }}' },
],
});
+62
View File
@@ -0,0 +1,62 @@
import { cli } from '@jackwener/opencli/registry';
cli({
site: 'nowcoder',
name: 'detail',
access: 'read',
description: 'Post detail view (supports ID / UUID / URL)',
domain: 'www.nowcoder.com',
args: [
{ name: 'id', positional: true, required: true, help: 'Post ID, UUID, or URL' },
],
columns: ['title', 'author', 'school', 'content', 'likes', 'comments', 'views', 'time', 'location'],
pipeline: [
{ navigate: 'https://www.nowcoder.com' },
{ evaluate: `(async () => {
const raw = \${{ args.id | json }};
const base = 'https://gw-c.nowcoder.com';
const strip = (html) => (html || '').replace(/<[^>]+>/g, '').replace(/&nbsp;/g, ' ').replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&amp;/g, '&').trim();
let id = raw;
const urlMatch = raw.match(/discuss\\/(\\d+)/);
if (urlMatch) id = urlMatch[1];
let data = null;
if (/[a-f]/.test(id) && id.length > 20) {
const r = await fetch(base + '/api/sparta/detail/moment-data/detail/' + id, {credentials: 'include'});
const d = await r.json();
if (d.success && d.data) data = d.data;
}
if (!data && /^\\d+$/.test(id)) {
const r = await fetch(base + '/api/sparta/detail/content-data/detail/' + id, {credentials: 'include'});
const d = await r.json();
if (d.success && d.data) data = d.data;
}
if (!data && /^\\d+$/.test(id)) {
const r = await fetch(base + '/api/sparta/detail/moment-data/detail/' + id, {credentials: 'include'});
const d = await r.json();
if (d.success && d.data) data = d.data;
}
if (!data) throw new Error('Post not found: ' + id);
const user = data.userBrief || {};
const freq = data.frequencyData || {};
return [{
title: data.title || '(untitled)',
author: user.nickname || '',
school: user.educationInfo || '',
content: strip(data.content || '').substring(0, 500),
likes: freq.likeCnt || 0,
comments: freq.commentCnt || freq.totalCommentCnt || 0,
views: freq.viewCnt || 0,
time: data.createdAt ? new Date(data.createdAt).toISOString().slice(0, 19) : '',
location: data.ip4Location || '',
}];
})()
` },
],
});
+37
View File
@@ -0,0 +1,37 @@
import { cli } from '@jackwener/opencli/registry';
cli({
site: 'nowcoder',
name: 'experience',
access: 'read',
description: 'Interview experience posts',
domain: 'www.nowcoder.com',
args: [
{ name: 'page', type: 'int', default: 1, help: 'Page number' },
{ name: 'limit', type: 'int', default: 15, help: 'Number of items' },
],
columns: ['rank', 'title', 'author', 'school', 'likes', 'comments', 'views', 'id'],
pipeline: [
{ navigate: 'https://www.nowcoder.com' },
{ evaluate: `(async () => {
const page = \${{ args.page }};
const limit = \${{ args.limit }};
const r = await fetch('https://gw-c.nowcoder.com/api/sparta/home/tab/content?tabId=818&categoryType=1&pageNo=' + page + '&pageSize=' + limit, {credentials: 'include'});
const d = await r.json();
if (!d.success) throw new Error(d.msg || 'API failed');
return (d.data?.records || []).map((item, i) => ({
rank: i + 1,
title: item.contentData?.title || '',
author: item.userBrief?.nickname || '',
school: item.userBrief?.educationInfo || '',
likes: item.frequencyData?.likeCnt || 0,
comments: item.frequencyData?.commentCnt || 0,
views: item.frequencyData?.viewCnt || 0,
id: item.contentData?.uuid || item.contentData?.id || item.contentId || '',
}));
})()
` },
{ filter: 'item.title' },
{ limit: '${{ args.limit }}' },
],
});
+25
View File
@@ -0,0 +1,25 @@
import { cli, Strategy } from '@jackwener/opencli/registry';
cli({
site: 'nowcoder',
name: 'hot',
access: 'read',
description: 'Hot search ranking',
domain: 'www.nowcoder.com',
strategy: Strategy.PUBLIC,
browser: false,
args: [
{ name: 'limit', type: 'int', default: 10, help: 'Number of items' },
],
columns: ['rank', 'title', 'heat'],
pipeline: [
{ fetch: { url: 'https://gw-c.nowcoder.com/api/sparta/hot-search/hot-content' } },
{ select: 'data.hotQuery' },
{ map: {
rank: '${{ item.rank }}',
title: '${{ item.query }}',
heat: '${{ item.hotValue }}',
} },
{ limit: '${{ args.limit }}' },
],
});
+22
View File
@@ -0,0 +1,22 @@
import { cli, Strategy } from '@jackwener/opencli/registry';
cli({
site: 'nowcoder',
name: 'jobs',
access: 'read',
description: 'Career category listing',
domain: 'www.nowcoder.com',
strategy: Strategy.PUBLIC,
browser: false,
args: [],
columns: ['id', 'career', 'learners'],
pipeline: [
{ fetch: { url: 'https://gw-c.nowcoder.com/api/sparta/company-question/careerJobLevel1List' } },
{ select: 'data.careerJobSelectors' },
{ map: {
id: '${{ item.id }}',
career: '${{ item.name }}',
learners: `\${{ item.practiceCount || '' }}`,
} },
],
});
+30
View File
@@ -0,0 +1,30 @@
import { cli } from '@jackwener/opencli/registry';
cli({
site: 'nowcoder',
name: 'notifications',
access: 'read',
description: 'Unread message summary',
domain: 'www.nowcoder.com',
args: [],
columns: ['type', 'unread'],
pipeline: [
{ navigate: 'https://www.nowcoder.com' },
{ evaluate: `(async () => {
const r = await fetch('https://gw-c.nowcoder.com/api/sparta/message/pc/unread/detail', {credentials: 'include'});
const d = await r.json();
if (!d.success) throw new Error(d.msg || 'API failed');
const data = d.data;
return [
{type: 'system', unread: data.systemNotice?.unreadCount || 0},
{type: 'likes', unread: data.likeCollect?.unreadCount || 0},
{type: 'comments', unread: data.commentMessage?.unreadCount || 0},
{type: 'follows', unread: data.followMessage?.unreadCount || 0},
{type: 'messages', unread: data.privateMessage?.unreadCount || 0},
{type: 'job_apply', unread: data.nowPickJobApply?.unreadCount || 0},
{type: 'total', unread: data.total?.unreadCount || 0},
];
})()
` },
],
});
+41
View File
@@ -0,0 +1,41 @@
import { cli } from '@jackwener/opencli/registry';
cli({
site: 'nowcoder',
name: 'papers',
access: 'read',
description: 'Interview question bank by company and job',
domain: 'www.nowcoder.com',
args: [
{ name: 'job', type: 'str', default: '11002', help: 'Job ID (11002=Java, 11003=C++, 11200=Backend, 11203=QA, 11201=Frontend)' },
{ name: 'company', type: 'str', default: '', help: 'Company ID (e.g. 139=Baidu, 138=Tencent, 239=Huawei)' },
{ name: 'limit', type: 'int', default: 10, help: 'Number of items' },
],
columns: ['rank', 'title', 'company', 'practitioners'],
pipeline: [
{ navigate: 'https://www.nowcoder.com' },
{ evaluate: `(async () => {
const jobId = parseInt(\${{ args.job | json }});
const companyId = \${{ args.company | json }};
const limit = \${{ args.limit }};
const body = {jobId, page: 1, pageSize: limit};
if (companyId) body.companyId = parseInt(companyId);
const r = await fetch('https://gw-c.nowcoder.com/api/sparta/company-question/get-paper-list', {
method: 'POST',
credentials: 'include',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(body)
});
const d = await r.json();
if (!d.success) throw new Error(d.msg || 'API failed');
return (d.data?.records || []).map((p, i) => ({
rank: i + 1,
title: p.paperName || '',
company: p.companyTag?.name || '',
practitioners: p.practiceCnt || 0,
}));
})()
` },
{ limit: '${{ args.limit }}' },
],
});
+38
View File
@@ -0,0 +1,38 @@
import { cli } from '@jackwener/opencli/registry';
cli({
site: 'nowcoder',
name: 'practice',
access: 'read',
description: 'Categorized practice questions with progress',
domain: 'www.nowcoder.com',
args: [
{ name: 'job', type: 'str', default: '11226', help: 'Career ID (11226=Software, 11227=Hardware, 11229=Product, 11230=Finance)' },
{ name: 'limit', type: 'int', default: 20, help: 'Number of items' },
],
columns: ['category', 'subject', 'total', 'done', 'remaining'],
pipeline: [
{ navigate: 'https://www.nowcoder.com' },
{ evaluate: `(async () => {
const jobId = \${{ args.job | json }};
const limit = \${{ args.limit }};
const r = await fetch('https://gw-c.nowcoder.com/api/sparta/intelligent/getPCIntelligentList?jobId=' + jobId, {credentials: 'include'});
const d = await r.json();
if (!d.success) throw new Error(d.msg || 'API failed');
const all = [];
for (const tag of (d.data?.tags || [])) {
for (const item of (tag.items || [])) {
all.push({
category: tag.title || 'recommended',
subject: item.title,
total: item.tcount,
done: item.rcount,
remaining: item.leftCount,
});
}
}
return all.slice(0, limit);
})()
` },
],
});
+31
View File
@@ -0,0 +1,31 @@
import { cli, Strategy } from '@jackwener/opencli/registry';
cli({
site: 'nowcoder',
name: 'recommend',
access: 'read',
description: 'Recommended feed',
domain: 'www.nowcoder.com',
strategy: Strategy.PUBLIC,
browser: false,
args: [
{ name: 'page', type: 'int', default: 1, help: 'Page number' },
{ name: 'limit', type: 'int', default: 15, help: 'Number of items' },
],
columns: ['rank', 'title', 'author', 'likes', 'comments', 'views', 'id'],
pipeline: [
{ fetch: { url: 'https://gw-c.nowcoder.com/api/sparta/home/recommend?page=${{ args.page }}&size=${{ args.limit }}' } },
{ select: 'data.records' },
{ map: {
rank: '${{ index + 1 }}',
title: `\${{ item.momentData?.title || item.longContentData?.title || item.contentData?.title || '' }}`,
author: `\${{ item.userBrief?.nickname || '' }}`,
likes: '${{ item.frequencyData?.likeCnt || 0 }}',
comments: '${{ item.frequencyData?.commentCnt || 0 }}',
views: '${{ item.frequencyData?.viewCnt || 0 }}',
id: `\${{ item.momentData?.uuid || item.longContentData?.uuid || item.contentData?.uuid || item.contentId || '' }}`,
} },
{ filter: 'item.title' },
{ limit: '${{ args.limit }}' },
],
});
+40
View File
@@ -0,0 +1,40 @@
import { cli } from '@jackwener/opencli/registry';
cli({
site: 'nowcoder',
name: 'referral',
access: 'read',
description: 'Internal referral posts',
domain: 'www.nowcoder.com',
args: [
{ name: 'page', type: 'int', default: 1, help: 'Page number' },
{ name: 'limit', type: 'int', default: 15, help: 'Number of items' },
],
columns: ['rank', 'title', 'author', 'school', 'likes', 'comments', 'views', 'id'],
pipeline: [
{ navigate: 'https://www.nowcoder.com' },
{ evaluate: `(async () => {
const page = \${{ args.page }};
const limit = \${{ args.limit }};
const r = await fetch('https://gw-c.nowcoder.com/api/sparta/home/tab/content?tabId=861&categoryType=1&pageNo=' + page + '&pageSize=' + limit, {credentials: 'include'});
const d = await r.json();
if (!d.success) throw new Error(d.msg || 'API failed');
return (d.data?.records || []).map((item, i) => {
const content = item.contentData || item.momentData || {};
return {
rank: i + 1,
title: content.title || '',
author: item.userBrief?.nickname || '',
school: item.userBrief?.educationInfo || '',
likes: item.frequencyData?.likeCnt || 0,
comments: item.frequencyData?.commentCnt || 0,
views: item.frequencyData?.viewCnt || 0,
id: item.momentData?.uuid || item.contentData?.uuid || item.contentId || '',
};
});
})()
` },
{ filter: 'item.title' },
{ limit: '${{ args.limit }}' },
],
});
+41
View File
@@ -0,0 +1,41 @@
import { cli } from '@jackwener/opencli/registry';
cli({
site: 'nowcoder',
name: 'salary',
access: 'read',
description: 'Salary disclosure posts',
domain: 'www.nowcoder.com',
args: [
{ name: 'page', type: 'int', default: 1, help: 'Page number' },
{ name: 'limit', type: 'int', default: 15, help: 'Number of items' },
],
columns: ['rank', 'title', 'author', 'school', 'likes', 'comments', 'views', 'id'],
pipeline: [
{ navigate: 'https://www.nowcoder.com' },
{ evaluate: `(async () => {
const page = \${{ args.page }};
const limit = \${{ args.limit }};
const r = await fetch('https://gw-c.nowcoder.com/api/sparta/home/tab/content?tabId=858&categoryType=1&pageNo=' + page + '&pageSize=' + limit, {credentials: 'include'});
const d = await r.json();
if (!d.success) throw new Error(d.msg || 'API failed');
return (d.data?.records || []).map((item, i) => {
const moment = item.momentData || {};
const content = item.contentData || {};
return {
rank: i + 1,
title: moment.title || content.title || '',
author: item.userBrief?.nickname || '',
school: item.userBrief?.educationInfo || '',
likes: item.frequencyData?.likeCnt || 0,
comments: item.frequencyData?.commentCnt || 0,
views: item.frequencyData?.viewCnt || 0,
id: moment.uuid || content.uuid || item.contentId || '',
};
});
})()
` },
{ filter: 'item.title' },
{ limit: '${{ args.limit }}' },
],
});
+50
View File
@@ -0,0 +1,50 @@
import { cli } from '@jackwener/opencli/registry';
cli({
site: 'nowcoder',
name: 'search',
access: 'read',
description: 'Full-text search',
domain: 'www.nowcoder.com',
args: [
{ name: 'query', positional: true, required: true, help: 'Search keyword' },
{ name: 'type', type: 'str', default: 'all', help: 'Search type (all/post/question/user/job)' },
{ name: 'limit', type: 'int', default: 10, help: 'Number of results' },
],
columns: ['rank', 'title', 'author', 'school', 'content', 'id'],
pipeline: [
{ navigate: 'https://www.nowcoder.com' },
{ evaluate: `(async () => {
const query = \${{ args.query | json }};
const type = \${{ args.type | json }};
const limit = \${{ args.limit }};
const strip = (html) => (html || '').replace(/<[^>]+>/g, '').replace(/&amp;/g, '&').replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&nbsp;/g, ' ').trim();
const r = await fetch('https://gw-c.nowcoder.com/api/sparta/pc/search', {
method: 'POST',
credentials: 'include',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({query, type, page: 1, pageSize: limit})
});
const d = await r.json();
if (!d.success) throw new Error(d.msg || 'search failed');
return (d.data?.records || []).map((item, i) => {
const data = item.data || {};
const moment = data.momentData || {};
const contentData = data.contentData || {};
const user = data.userBrief || {};
const uuid = moment.uuid || contentData.uuid || '';
const id = data.contentId || '';
return {
rank: i + 1,
title: moment.title || contentData.title || user.nickname || '',
author: user.nickname || '',
school: user.educationInfo || '',
content: strip(moment.content || contentData.content || ''),
id: uuid || id,
};
}).filter(r => r.title);
})()
` },
{ limit: '${{ args.limit }}' },
],
});
+34
View File
@@ -0,0 +1,34 @@
import { cli } from '@jackwener/opencli/registry';
cli({
site: 'nowcoder',
name: 'suggest',
access: 'read',
description: 'Search suggestions',
domain: 'www.nowcoder.com',
args: [
{ name: 'query', positional: true, required: true, help: 'Search keyword' },
],
columns: ['rank', 'suggestion', 'type'],
pipeline: [
{ navigate: 'https://www.nowcoder.com' },
{ evaluate: `(async () => {
const query = \${{ args.query | json }};
const r = await fetch('https://gw-c.nowcoder.com/api/sparta/search/suggest', {
method: 'POST',
credentials: 'include',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({query})
});
const d = await r.json();
if (!d.success) throw new Error(d.msg || 'suggest failed');
return (d.data?.records || []).map((item, i) => ({
rank: i + 1,
suggestion: item.name || '',
type: item.typeName || 'general',
}));
})()
` },
{ limit: '10' },
],
});
+28
View File
@@ -0,0 +1,28 @@
import { cli, Strategy } from '@jackwener/opencli/registry';
cli({
site: 'nowcoder',
name: 'topics',
access: 'read',
description: 'Hot discussion topics',
domain: 'www.nowcoder.com',
strategy: Strategy.PUBLIC,
browser: false,
args: [
{ name: 'limit', type: 'int', default: 10, help: 'Number of items' },
],
columns: ['rank', 'topic', 'views', 'posts', 'heat', 'id'],
pipeline: [
{ fetch: { url: 'https://gw-c.nowcoder.com/api/sparta/subject/hot-subject' } },
{ select: 'data.result' },
{ map: {
rank: '${{ index + 1 }}',
topic: '${{ item.content }}',
views: '${{ item.viewCount }}',
posts: '${{ item.momentCount }}',
heat: '${{ item.hotValue }}',
id: '${{ item.uuid || item.id || "" }}',
} },
{ limit: '${{ args.limit }}' },
],
});
+26
View File
@@ -0,0 +1,26 @@
import { cli, Strategy } from '@jackwener/opencli/registry';
cli({
site: 'nowcoder',
name: 'trending',
access: 'read',
description: 'Trending posts',
domain: 'www.nowcoder.com',
strategy: Strategy.PUBLIC,
browser: false,
args: [
{ name: 'limit', type: 'int', default: 10, help: 'Number of items' },
],
columns: ['rank', 'title', 'heat', 'id'],
pipeline: [
{ fetch: { url: 'https://gw-c.nowcoder.com/api/sparta/hot-search/top-hot-pc' } },
{ select: 'data.result' },
{ map: {
rank: '${{ index + 1 }}',
title: '${{ item.title }}',
heat: '${{ item.hotValueFromDolphin }}',
id: '${{ item.uuid || item.id || "" }}',
} },
{ limit: '${{ args.limit }}' },
],
});