fix(route): linkedin.cn Fix the job desc section formatting error with renderer by attributes. (#11736)
* fix(route): xiaomiyoupin crowdfunding error and migrate to v2 * initial commit * add searchHit * add jobPosting * linkedincn * add linkedincn ok * feat(route): 中国新闻周刊 (#11665) * feat(route): 中国新闻周刊 * fix: decode via iconv instead of puppeteer * fix: cache full text request * Update lib/v2/inewsweek/index.js * fix: exact publish time * fix: timezone * feat(route): 修复 HelloGitHub 的 月刊 路由 (#11675) * feat(route): 添加 百度股市通 * fix: 修改 百度股市通 路径 * docs: 添加 百度股市通 文档 * feat: 添加 radar 支持 * fix: 按字母顺序插入新路由 * feat(route): 新增 腾讯新闻 - 新型冠状病毒肺炎疫情实时追踪 * fix: 优化 腾讯新闻 - 新型冠状病毒肺炎疫情实时追踪 的标题 * fix: 修复 腾讯新闻 - 新型冠状病毒肺炎疫情实时追踪 部分情况下的非空判断 * fix: 修复 地区名称标题的问题 * fix: 腾讯新闻 - 新型冠状病毒肺炎疫情实时追踪 的 guid 增加 pubDate * fix: 修复 腾讯新闻 - 新型冠状病毒肺炎疫情实时追踪 guid 中添加 pubDate * fix: 修改 腾讯新闻 - 新型冠状病毒肺炎疫情实时追踪 的 title * feat(route): 修复 HelloGitHub 的 月刊 路由 * fix: remove guid * fix(route): x6d (#11676) * chore(deps): bump rand-user-agent from 1.0.101 to 1.0.102 (#11678) Bumps [rand-user-agent](https://github.com/WebScrapingAPI/rand-user-agent) from 1.0.101 to 1.0.102. - [Release notes](https://github.com/WebScrapingAPI/rand-user-agent/releases) - [Commits](https://github.com/WebScrapingAPI/rand-user-agent/commits) --- updated-dependencies: - dependency-name: rand-user-agent dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump @sentry/node from 7.31.1 to 7.32.1 (#11680) Bumps [@sentry/node](https://github.com/getsentry/sentry-javascript) from 7.31.1 to 7.32.1. - [Release notes](https://github.com/getsentry/sentry-javascript/releases) - [Changelog](https://github.com/getsentry/sentry-javascript/blob/master/CHANGELOG.md) - [Commits](https://github.com/getsentry/sentry-javascript/compare/7.31.1...7.32.1) --- updated-dependencies: - dependency-name: "@sentry/node" dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump jsdom from 21.0.0 to 21.1.0 (#11681) Bumps [jsdom](https://github.com/jsdom/jsdom) from 21.0.0 to 21.1.0. - [Release notes](https://github.com/jsdom/jsdom/releases) - [Changelog](https://github.com/jsdom/jsdom/blob/master/Changelog.md) - [Commits](https://github.com/jsdom/jsdom/compare/21.0.0...21.1.0) --- updated-dependencies: - dependency-name: jsdom dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump puppeteer from 19.5.2 to 19.6.0 (#11679) Bumps [puppeteer](https://github.com/puppeteer/puppeteer) from 19.5.2 to 19.6.0. - [Release notes](https://github.com/puppeteer/puppeteer/releases) - [Changelog](https://github.com/puppeteer/puppeteer/blob/main/release-please-config.json) - [Commits](https://github.com/puppeteer/puppeteer/compare/puppeteer-v19.5.2...puppeteer-v19.6.0) --- updated-dependencies: - dependency-name: puppeteer dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * feat(route): famitsu (#11682) * fix(route): xiaomiyoupin crowdfunding error and migrate to v2 (#11677) * fix(route): xiaomiyoupin crowdfunding error and migrate to v2 * Update lib/v2/xiaomiyoupin/maintainer.js * Update docs/shopping.md * use single api endpoint for both module * add pubDate() * fix(route): ikea (#11685) * chore(deps): bump @sentry/node from 7.32.1 to 7.33.0 (#11687) Bumps [@sentry/node](https://github.com/getsentry/sentry-javascript) from 7.32.1 to 7.33.0. - [Release notes](https://github.com/getsentry/sentry-javascript/releases) - [Changelog](https://github.com/getsentry/sentry-javascript/blob/master/CHANGELOG.md) - [Commits](https://github.com/getsentry/sentry-javascript/compare/7.32.1...7.33.0) --- updated-dependencies: - dependency-name: "@sentry/node" dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps-dev): bump jest from 29.3.1 to 29.4.0 (#11686) Bumps [jest](https://github.com/facebook/jest/tree/HEAD/packages/jest) from 29.3.1 to 29.4.0. - [Release notes](https://github.com/facebook/jest/releases) - [Changelog](https://github.com/facebook/jest/blob/main/CHANGELOG.md) - [Commits](https://github.com/facebook/jest/commits/v29.4.0/packages/jest) --- updated-dependencies: - dependency-name: jest dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix(route): bloomberg videopage parse error (#11688) * fix(route): clickme (#11689) * fix(route): lofter (#11696) * fix(route): caixin blog (#11695) * fix(route): caixin blog * fix: url * chore(deps): bump ioredis from 5.2.5 to 5.3.0 (#11697) Bumps [ioredis](https://github.com/luin/ioredis) from 5.2.5 to 5.3.0. - [Release notes](https://github.com/luin/ioredis/releases) - [Changelog](https://github.com/luin/ioredis/blob/main/CHANGELOG.md) - [Commits](https://github.com/luin/ioredis/compare/v5.2.5...v5.3.0) --- updated-dependencies: - dependency-name: ioredis dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix(route): bioone (#11699) * feat(route): github wiki history (#11700) * feat(route): wuhan (#11701) Co-authored-by: nczitzk <nczitzk@gmail.com> * chore(deps): bump puppeteer from 19.6.0 to 19.6.1 (#11702) Bumps [puppeteer](https://github.com/puppeteer/puppeteer) from 19.6.0 to 19.6.1. - [Release notes](https://github.com/puppeteer/puppeteer/releases) - [Changelog](https://github.com/puppeteer/puppeteer/blob/main/release-please-config.json) - [Commits](https://github.com/puppeteer/puppeteer/compare/puppeteer-v19.6.0...puppeteer-v19.6.1) --- updated-dependencies: - dependency-name: puppeteer dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps-dev): bump jest from 29.4.0 to 29.4.1 (#11703) Bumps [jest](https://github.com/facebook/jest/tree/HEAD/packages/jest) from 29.4.0 to 29.4.1. - [Release notes](https://github.com/facebook/jest/releases) - [Changelog](https://github.com/facebook/jest/blob/main/CHANGELOG.md) - [Commits](https://github.com/facebook/jest/commits/v29.4.1/packages/jest) --- updated-dependencies: - dependency-name: jest dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix(route): fix date err and refactor to v2 (#11705) * fix(route): fix date err and refactor to v2 * docs: add en docs * fix: typo * feat(route): add mixcloud (#11672) * feat(route): add mixcloud * docs * feat(route): add mixcloud * convert linebreaks to <br> * docs: add supportPodcast * codestyle: use template literals * refactor: split graph ql queries * fix(route): 软餐 (#11708) * feat(route): gelonghui (#11710) * chore(deps): bump @sentry/node from 7.33.0 to 7.34.0 (#11711) Bumps [@sentry/node](https://github.com/getsentry/sentry-javascript) from 7.33.0 to 7.34.0. - [Release notes](https://github.com/getsentry/sentry-javascript/releases) - [Changelog](https://github.com/getsentry/sentry-javascript/blob/master/CHANGELOG.md) - [Commits](https://github.com/getsentry/sentry-javascript/compare/7.33.0...7.34.0) --- updated-dependencies: - dependency-name: "@sentry/node" dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix(route): fix not unique guid and refactor to v2 (#11712) * add null check * add more null check for compensation * remove unused key * refactor: change route * fix typo * add desc text formatting and caching * posting forget to accept * Fix async/await mismatch * Fix async/await mismatch on ctx.cache.tryGet ---------
This commit is contained in:
parent
626124728e
commit
a47a886f29
|
|
@ -4,7 +4,7 @@ const siteUrl = 'https://www.linkedin.cn/incareer/jobs/search';
|
|||
|
||||
module.exports = async (ctx) => {
|
||||
const { title, jobs } = await parseSearchHit(ctx);
|
||||
const items = await Promise.all(jobs.map((job) => parseJobPosting(job)));
|
||||
const items = await Promise.all(jobs.map((job) => parseJobPosting(ctx, job)));
|
||||
ctx.state.data = {
|
||||
title: `领英 - ${title}`,
|
||||
link: siteUrl,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,163 @@
|
|||
const text_tag = {
|
||||
LINE_BREAK: 0,
|
||||
INLINE_CODE: 1,
|
||||
LEGACY_PUBLISHING_EMPHASIS: 2,
|
||||
LIST_ITEM: 3,
|
||||
LIST: 4,
|
||||
STRIKETHROUGH: 5,
|
||||
SUBSCRIPT: 6,
|
||||
SUPERSCRIPT: 7,
|
||||
UNDERLINE: 8,
|
||||
BOLD: 9,
|
||||
ITALIC: 10,
|
||||
PARAGRAPH: 11,
|
||||
};
|
||||
|
||||
class TreeNode {
|
||||
constructor(attr) {
|
||||
this.attr = attr;
|
||||
this.start = attr.start;
|
||||
this.end = attr.start + attr.length;
|
||||
this.children = [];
|
||||
}
|
||||
}
|
||||
class Ucs2Text {
|
||||
constructor(text) {
|
||||
if (Array.isArray(text)) {
|
||||
this.string = null;
|
||||
this.codePoints = text;
|
||||
} else {
|
||||
this.string = text;
|
||||
const k = text.length;
|
||||
const d = [];
|
||||
|
||||
for (let l, e, m = 0; m < k; ) {
|
||||
55296 <= (l = text.charCodeAt(m++)) && 56319 >= l && m < k ? (56320 === (64512 & (e = text.charCodeAt(m++))) ? d.push(((1023 & l) << 10) + (1023 & e) + 65536) : (d.push(l), m--)) : d.push(l);
|
||||
}
|
||||
this.codePoints = d;
|
||||
}
|
||||
this.length = this.codePoints.length;
|
||||
}
|
||||
substring(start, end) {
|
||||
let _len = this.length,
|
||||
_start = start,
|
||||
_end = end;
|
||||
if (_end === 0) {
|
||||
return new Ucs2Text('');
|
||||
}
|
||||
if (((isNaN(_start) || 0 > _start) && (_start = 0), (isNaN(_end) || 0 > _end) && (_end = _len), _start > _len && (_start = _len), _end > _len && (_end = _len), _end < _start)) {
|
||||
_len = [_start, _end];
|
||||
_end = _len[0];
|
||||
_start = _len[1];
|
||||
}
|
||||
return new Ucs2Text(_start === _end ? '' : this.codePoints.slice(_start, _end));
|
||||
}
|
||||
slice(a, b) {
|
||||
return this.substring(a, b).toString();
|
||||
}
|
||||
toString() {
|
||||
return null !== this.string
|
||||
? this.string
|
||||
: this.codePoints
|
||||
.map((a) => {
|
||||
let b = '';
|
||||
return 65535 < a && ((b += String.fromCharCode((((a -= 65536) >>> 10) & 1023) | 55296)), (a = 1023 & (56320 | a))), b + String.fromCharCode(a);
|
||||
})
|
||||
.join('');
|
||||
}
|
||||
}
|
||||
|
||||
const parseAttr = (description) => {
|
||||
const { attributes, text } = description;
|
||||
const attrs = [...attributes];
|
||||
attrs.sort((b, d) => {
|
||||
const f = b.start + b.length,
|
||||
e = d.start + d.length;
|
||||
return f !== e ? f - e : text_tag[b.detailData.style] - text_tag[d.detailData.style];
|
||||
});
|
||||
const n = [];
|
||||
for (const q of attrs) {
|
||||
const p = new TreeNode(q);
|
||||
for (let w = q.start; 0 < n.length && n[n.length - 1].attr.start >= w; ) {
|
||||
p.children.push(n.pop());
|
||||
}
|
||||
p.children.reverse();
|
||||
n.push(p);
|
||||
}
|
||||
|
||||
const m = new Ucs2Text(text);
|
||||
const renderSingle = (node) => {
|
||||
const style = node?.attr?.detailData?.style;
|
||||
switch (style) {
|
||||
case 'BOLD':
|
||||
return `<b>${node.text}</b>`;
|
||||
case 'LINE_BREAK':
|
||||
return `<br>`;
|
||||
case 'LIST_ITEM':
|
||||
return `<li>${node.text}</li>`;
|
||||
case 'LIST':
|
||||
return `<ul>${node.text}</ul>`;
|
||||
case 'INLINE_CODE':
|
||||
return `<code>${node.text}</code>`;
|
||||
case 'LEGACY_PUBLISHING_EMPHASIS':
|
||||
return `<em>${node.text}</em>`;
|
||||
case 'STRIKETHROUGH':
|
||||
return `<s>${node.text}</s>`;
|
||||
case 'SUBSCRIPT':
|
||||
return `<sub>${node.text}</sub>`;
|
||||
case 'SUPERSCRIPT':
|
||||
return `<sup>${node.text}</sup>`;
|
||||
case 'UNDERLINE':
|
||||
return `<u>${node.text}</u>`;
|
||||
case 'ITALIC':
|
||||
return `<i>${node.text}</i>`;
|
||||
case 'PARAGRAPH':
|
||||
return `<p>${node.text}</p>`;
|
||||
default:
|
||||
return node.text || node;
|
||||
}
|
||||
};
|
||||
|
||||
const render = (node) => {
|
||||
// DFS render
|
||||
if (node.children.length === 0) {
|
||||
node.text = m.slice(node.start, node.end);
|
||||
return renderSingle(node);
|
||||
}
|
||||
const res = [];
|
||||
let s = node.start;
|
||||
for (const child of node.children) {
|
||||
if (s < child.start) {
|
||||
res.push(m.slice(s, child.start)); // added text before 1st child node
|
||||
}
|
||||
s = child.end;
|
||||
res.push(render(child));
|
||||
}
|
||||
|
||||
if (s < node.end) {
|
||||
res.push(m.slice(s, node.end)); // added text after the last child node
|
||||
}
|
||||
node.text = res.join('');
|
||||
return renderSingle(node);
|
||||
};
|
||||
|
||||
const q = [];
|
||||
let p = 0;
|
||||
for (let w = 0; w < n.length; w++) {
|
||||
// BFS render
|
||||
const e = n[w];
|
||||
if (e.start > p) {
|
||||
q.push(m.slice(p, e.start));
|
||||
}
|
||||
p = e.end;
|
||||
q.push(render(e));
|
||||
}
|
||||
if (p < m.length) {
|
||||
q.push(m.slice(p, m.length));
|
||||
}
|
||||
return q.join('');
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
parseAttr,
|
||||
};
|
||||
|
|
@ -2,6 +2,7 @@ const crypto = require('crypto');
|
|||
const path = require('path');
|
||||
const { art } = require('@/utils/render');
|
||||
const got = require('@/utils/got');
|
||||
const { parseAttr } = require('./renderer');
|
||||
|
||||
const apiUrl = 'https://www.linkedin.cn/karpos/api/graphql';
|
||||
const searchHitQueryId = 'searchSearchHitsByJob.be362cd720abd0ebf89b4bbc3253047f';
|
||||
|
|
@ -90,27 +91,29 @@ const parseSearchHit = async (ctx) => {
|
|||
};
|
||||
};
|
||||
|
||||
const parseJobPosting = async (jobPosting) => {
|
||||
const parseJobPosting = (ctx, jobPosting) => {
|
||||
const entityUrn = jobPosting.entityUrn;
|
||||
const variables = {
|
||||
jobPostingUrn: entityUrn,
|
||||
};
|
||||
const resp = await got.post(apiUrl, {
|
||||
headers: makeHeader(),
|
||||
body: makeBody({
|
||||
operationName: 'jobViewPage',
|
||||
variables: makeVariables(variables),
|
||||
queryId: jobPostingQueryId,
|
||||
}),
|
||||
return ctx.cache.tryGet(`linkedincn:${entityUrn}`, async () => {
|
||||
const resp = await got.post(apiUrl, {
|
||||
headers: makeHeader(),
|
||||
body: makeBody({
|
||||
operationName: 'jobViewPage',
|
||||
variables: makeVariables({
|
||||
jobPostingUrn: entityUrn,
|
||||
}),
|
||||
queryId: jobPostingQueryId,
|
||||
}),
|
||||
});
|
||||
const job = resp.data.data.jobsJobPostingsById;
|
||||
job.desc = parseAttr(job.description);
|
||||
return {
|
||||
title: `${jobPosting.companyName} 正在找 ${jobPosting.title}`,
|
||||
link: `https://www.linkedin.cn/incareer/jobs/view/${entityUrn.split(':').pop()}`,
|
||||
guid: `linkedincn:${entityUrn}`,
|
||||
description: art(path.join(__dirname, '../templates/cn/posting.art'), job),
|
||||
pubDate: jobPosting.listedAt,
|
||||
};
|
||||
});
|
||||
const job = resp.data.data.jobsJobPostingsById;
|
||||
return {
|
||||
title: `${jobPosting.companyName} 正在找 ${jobPosting.title}`,
|
||||
link: `https://www.linkedin.cn/incareer/jobs/view/${entityUrn.split(':').pop()}`,
|
||||
guid: `linkedincn:${entityUrn}`,
|
||||
description: art(path.join(__dirname, '../templates/cn/posting.art'), job),
|
||||
pubDate: jobPosting.listedAt,
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
<h2>公司介绍</h2>
|
||||
<p>{{company.name}}</p>
|
||||
<p>员工人数:{{company.employeeCount}}</p>
|
||||
<p style="white-space: pre-line">{{company.localizedDescription}}</p>
|
||||
<p>{{company.localizedDescription}}</p>
|
||||
{{ /if }}
|
||||
<h2>职位介绍</h2>
|
||||
<p style="white-space: pre-line">{{description.text}}</p>
|
||||
<article>{{@ desc}}</article>
|
||||
|
|
|
|||
Loading…
Reference in New Issue