Skip to content

Commit

Permalink
fix(route): linkresearcher (#17681)
Browse files Browse the repository at this point in the history
* 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
KarasuShin authored Nov 22, 2024
1 parent 5cb3437 commit 14d49ee
Show file tree
Hide file tree
Showing 4 changed files with 226 additions and 48 deletions.
154 changes: 108 additions & 46 deletions lib/routes/linkresearcher/index.ts
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,
};
}
10 changes: 8 additions & 2 deletions lib/routes/linkresearcher/namespace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: '領研',
},
};
7 changes: 7 additions & 0 deletions lib/routes/linkresearcher/templates/bilingual.art
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 }}
103 changes: 103 additions & 0 deletions lib/routes/linkresearcher/types.ts
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;

0 comments on commit 14d49ee

Please sign in to comment.