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
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:
@@ -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);
|
||||
},
|
||||
});
|
||||
@@ -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 }}',
|
||||
} },
|
||||
],
|
||||
});
|
||||
@@ -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 }}' },
|
||||
],
|
||||
});
|
||||
@@ -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(/ /g, ' ').replace(/</g, '<').replace(/>/g, '>').replace(/&/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 || '',
|
||||
}];
|
||||
})()
|
||||
` },
|
||||
],
|
||||
});
|
||||
@@ -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 }}' },
|
||||
],
|
||||
});
|
||||
@@ -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 }}' },
|
||||
],
|
||||
});
|
||||
@@ -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 || '' }}`,
|
||||
} },
|
||||
],
|
||||
});
|
||||
@@ -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},
|
||||
];
|
||||
})()
|
||||
` },
|
||||
],
|
||||
});
|
||||
@@ -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 }}' },
|
||||
],
|
||||
});
|
||||
@@ -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);
|
||||
})()
|
||||
` },
|
||||
],
|
||||
});
|
||||
@@ -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 }}' },
|
||||
],
|
||||
});
|
||||
@@ -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 }}' },
|
||||
],
|
||||
});
|
||||
@@ -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 }}' },
|
||||
],
|
||||
});
|
||||
@@ -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(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/ /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 }}' },
|
||||
],
|
||||
});
|
||||
@@ -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' },
|
||||
],
|
||||
});
|
||||
@@ -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 }}' },
|
||||
],
|
||||
});
|
||||
@@ -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 }}' },
|
||||
],
|
||||
});
|
||||
Reference in New Issue
Block a user