-
Notifications
You must be signed in to change notification settings - Fork 7.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* fix(route): linkresearcher * Update lib/routes/linkresearcher/index.ts Co-authored-by: Tony <TonyRL@users.noreply.github.com> * Update lib/routes/linkresearcher/index.ts Co-authored-by: Tony <TonyRL@users.noreply.github.com> * Update lib/routes/linkresearcher/index.ts Co-authored-by: Tony <TonyRL@users.noreply.github.com> * Update lib/routes/linkresearcher/namespace.ts Co-authored-by: Tony <TonyRL@users.noreply.github.com> * feat: bilingual support * feat: add author and doi ---------
- Loading branch information
1 parent
5cb3437
commit 14d49ee
Showing
4 changed files
with
226 additions
and
48 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,76 +1,138 @@ | ||
import { Route } from '@/types'; | ||
import got from '@/utils/got'; | ||
import qs from 'query-string'; | ||
import { ViewType, type Data, type DataItem, type Route } from '@/types'; | ||
import ofetch from '@/utils/ofetch'; | ||
import { parseDate } from '@/utils/parse-date'; | ||
import InvalidParameterError from '@/errors/types/invalid-parameter'; | ||
import crypto from 'crypto'; | ||
import type { Context } from 'hono'; | ||
import type { DetailResponse, SearchResultItem } from './types'; | ||
import cache from '@/utils/cache'; | ||
import { getCurrentPath } from '@/utils/helpers'; | ||
import { art } from '@/utils/render'; | ||
import path from 'node:path'; | ||
|
||
const __dirname = getCurrentPath(import.meta.url); | ||
const templatePath = path.join(__dirname, 'templates/bilingual.art'); | ||
|
||
const baseURL = 'https://www.linkresearcher.com'; | ||
const apiURL = `${baseURL}/api`; | ||
|
||
export const route: Route = { | ||
name: 'Articles', | ||
path: '/:params', | ||
name: 'Unknown', | ||
maintainers: ['y9c'], | ||
example: '/linkresearcher/category=theses&columns=Nature%20导读&subject=生物', | ||
maintainers: ['y9c', 'KarasuShin'], | ||
handler, | ||
view: ViewType.Articles, | ||
categories: ['journal'], | ||
parameters: { | ||
params: { | ||
description: 'search parameters, support `category`, `subject`, `columns`, `query`', | ||
}, | ||
}, | ||
zh: { | ||
name: '文章', | ||
}, | ||
'zh-TW': { | ||
name: '文章', | ||
}, | ||
}; | ||
|
||
async function handler(ctx) { | ||
// parse params | ||
async function handler(ctx: Context): Promise<Data> { | ||
const categoryMap = { theses: '论文', information: '新闻', careers: '职业' } as const; | ||
const params = ctx.req.param('params'); | ||
const query = qs.parse(params); | ||
|
||
const categoryMap = { theses: '论文', information: '新闻', careers: '职业' }; | ||
const category = query.category; | ||
let title = categoryMap[category]; | ||
|
||
// get XSRF token from main page | ||
const metaURL = `${baseURL}/${category}`; | ||
const metaResponse = await got(metaURL); | ||
const xsrfToken = metaResponse.headers['set-cookie'][0].split(';')[0].split('=')[1]; | ||
|
||
let data = { filters: { status: false } }; | ||
if (query.subject !== undefined && query.columns !== undefined) { | ||
data = { filters: { status: true, subject: query.subject, columns: query.columns } }; | ||
title = `${title}「${query.subject} & ${query.columns}」`; | ||
} else if (query.subject !== undefined && query.columns === undefined) { | ||
data = { filters: { status: true, subject: query.subject } }; | ||
title = `${title}「${query.subject}」`; | ||
} else if (query.subject === undefined && query.columns !== undefined) { | ||
data = { filters: { status: true, columns: query.columns } }; | ||
title = `${title}「${query.columns}」`; | ||
const filters = new URLSearchParams(params); | ||
|
||
const subject = filters.get('subject'); | ||
const columns = filters.get('columns'); | ||
const query = filters.get('query') ?? ''; | ||
const category = filters.get('category') ?? ('theses' as keyof typeof categoryMap); | ||
|
||
if (!(category in categoryMap)) { | ||
throw new InvalidParameterError('Invalid category'); | ||
} | ||
let title = categoryMap[category] as string; | ||
|
||
const token = crypto.randomUUID(); | ||
|
||
const data: { | ||
filters: { | ||
status: boolean; | ||
subject?: string; | ||
columns?: string; | ||
}; | ||
} = { filters: { status: true } }; | ||
|
||
if (subject) { | ||
data.filters.subject = subject; | ||
title = `${title}「${subject}」`; | ||
} | ||
data.query = query.query; | ||
|
||
if (columns) { | ||
data.filters.columns = columns; | ||
title = `${title}「${columns}」`; | ||
} | ||
|
||
const dataURL = `${baseURL}/api/${category === 'careers' ? 'articles' : category}/search`; | ||
const pageResponse = await got.post(dataURL, { | ||
const pageResponse = await ofetch<{ | ||
hits: SearchResultItem[]; | ||
}>(dataURL, { | ||
method: 'POST', | ||
headers: { | ||
'content-type': 'application/json; charset=UTF-8', | ||
'x-xsrf-token': xsrfToken, | ||
cookie: `XSRF-TOKEN=${xsrfToken}`, | ||
'x-xsrf-token': token, | ||
cookie: `XSRF-TOKEN=${token}`, | ||
}, | ||
searchParams: { | ||
params: { | ||
from: 0, | ||
size: 20, | ||
type: category === 'careers' ? 'CAREER' : 'SEARCH', | ||
}, | ||
json: data, | ||
body: { | ||
...data, | ||
query, | ||
}, | ||
}); | ||
|
||
const list = pageResponse.data.hits; | ||
const items = await Promise.all( | ||
pageResponse.hits.map((item) => { | ||
const link = `${baseURL}/${category}/${item.id}`; | ||
return cache.tryGet(link, async () => { | ||
const response = await ofetch<DetailResponse>(`${apiURL}/${category === 'theses' ? 'theses' : 'information'}/${item.id}`, { | ||
responseType: 'json', | ||
}); | ||
|
||
const dataItem: DataItem = { | ||
title: response.title, | ||
pubDate: parseDate(response.onlineTime), | ||
link, | ||
image: response.cover, | ||
}; | ||
|
||
dataItem.description = | ||
'zhTextList' in response && 'enTextList' in response | ||
? art(templatePath, { | ||
zh: response.zhTextList, | ||
en: response.enTextList, | ||
}) | ||
: response.content; | ||
|
||
if ('paperList' in response) { | ||
const { doi, authors } = response.paperList[0]; | ||
dataItem.doi = doi; | ||
dataItem.author = authors.map((author) => ({ name: author })); | ||
} | ||
|
||
const out = list.map((item) => ({ | ||
title: item.title, | ||
description: item.content, | ||
pubDate: parseDate(item.createdAt, 'x'), | ||
link: `${metaURL}/${item.id}`, | ||
guid: `${metaURL}/${item.id}`, | ||
doi: item.identCode === undefined ? '' : item.identCode, | ||
author: item.authors === undefined ? '' : item.authors.join(', '), | ||
})); | ||
return dataItem; | ||
}) as unknown as DataItem; | ||
}) | ||
); | ||
|
||
return { | ||
title: `领研 | ${title}`, | ||
description: | ||
'领研是链接华人学者的人才及成果平台。领研为国内外高校、科研机构及科技企业提供科研人才招聘服务,也是青年研究者的职业发展指导及线上培训平台;研究者还可将自己的研究论文上传至领研,与超过五十万华人学者分享工作的最新进展。', | ||
image: 'https://www.linkresearcher.com/assets/images/logo-app.png', | ||
image: `${baseURL}/assets/images/logo-app.png`, | ||
link: baseURL, | ||
item: out, | ||
item: items, | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
{{ each en }} | ||
{{ if $index !== 0 }} | ||
<br> | ||
{{ /if }} | ||
<p>{{ $value }}</p> | ||
<p>{{ zh[$index] }}</p> | ||
{{ /each }} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
interface BaseItem { | ||
id: string; | ||
title: string; | ||
tags: string[]; | ||
onlineTime: number; | ||
cover: string; | ||
} | ||
|
||
export interface InformationItem extends BaseItem { | ||
summary: string; | ||
} | ||
|
||
export interface ThesesItem extends BaseItem { | ||
authors: string[]; | ||
content: string; | ||
journals: string[]; | ||
publishDate: string; | ||
source: { | ||
sourceType: string; | ||
}; | ||
subject: string; | ||
thesisTitle: string; | ||
} | ||
|
||
export interface ArticleItem extends BaseItem { | ||
columns: string[]; | ||
source: { | ||
logo: string; | ||
sourceId: string; | ||
sourceName: string; | ||
sourceType: string; | ||
}; | ||
summary: string; | ||
type: string; | ||
} | ||
|
||
export type SearchResultItem = InformationItem | ThesesItem | ArticleItem; | ||
|
||
export interface ThesesDetailResponse { | ||
columns: string[]; | ||
content: string; | ||
cover: string; | ||
enTextList: string[]; | ||
id: string; | ||
journals: string[]; | ||
link: string; | ||
onlineTime: number; | ||
original: boolean; | ||
paperList: { | ||
authors: string[]; | ||
checkname: string; | ||
doi: string; | ||
id: string; | ||
journal: string; | ||
link: string; | ||
publishDate: string; | ||
subjects: string[]; | ||
summary: string; | ||
title: string; | ||
translateSummary: string; | ||
type: string; | ||
}[]; | ||
relevant: { | ||
timestamp: number; | ||
type: string; | ||
}[]; | ||
source: { | ||
sourceId: string; | ||
sourceName: string; | ||
}; | ||
sourceKey: string; | ||
sourceType: string; | ||
tags: string[]; | ||
template: boolean; | ||
title: string; | ||
userType: number; | ||
zhTextList?: string[]; | ||
} | ||
|
||
export interface InformationDetailResponse { | ||
columns: string[]; | ||
content: string; | ||
cover: string; | ||
id: string; | ||
onlineTime: number; | ||
original: boolean; | ||
relevant: { | ||
timestamp: number; | ||
type: string; | ||
}[]; | ||
source: { | ||
sourceId: string; | ||
sourceName: string; | ||
}; | ||
sourceKey: string; | ||
subject: string; | ||
summary: string; | ||
tags: string[]; | ||
title: string; | ||
type: string; | ||
} | ||
|
||
export type DetailResponse = ThesesDetailResponse | InformationDetailResponse; |