fix(route): linkresearcher (#17681)
* 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 ---------
This commit is contained in:
parent
5cb34379c3
commit
14d49eea38
|
|
@ -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 filters = new URLSearchParams(params);
|
||||
|
||||
const categoryMap = { theses: '论文', information: '新闻', careers: '职业' };
|
||||
const category = query.category;
|
||||
let title = categoryMap[category];
|
||||
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);
|
||||
|
||||
// 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}」`;
|
||||
if (!(category in categoryMap)) {
|
||||
throw new InvalidParameterError('Invalid category');
|
||||
}
|
||||
data.query = query.query;
|
||||
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}」`;
|
||||
}
|
||||
|
||||
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 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(', '),
|
||||
}));
|
||||
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 }));
|
||||
}
|
||||
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,12 @@ import type { Namespace } from '@/types';
|
|||
|
||||
export const namespace: Namespace = {
|
||||
name: 'Link Research',
|
||||
url: 'linkresearcher',
|
||||
lang: 'en',
|
||||
url: 'www.linkresearcher.com',
|
||||
lang: 'zh-CN',
|
||||
zh: {
|
||||
name: '领研',
|
||||
},
|
||||
'zh-TW': {
|
||||
name: '領研',
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
{{ each en }}
|
||||
{{ if $index !== 0 }}
|
||||
<br>
|
||||
{{ /if }}
|
||||
<p>{{ $value }}</p>
|
||||
<p>{{ zh[$index] }}</p>
|
||||
{{ /each }}
|
||||
|
|
@ -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;
|
||||
Loading…
Reference in New Issue