UNPKG

@seacolour/openalex-mcp-server-tool

Version:
494 lines (435 loc) 17.2 kB
import fetch from 'node-fetch'; /** * 将 abstract_inverted_index 转换为摘要文本。 * @param {Object} abstractInvertedIndex - 摘要的倒排索引对象 * @returns {string} - 转换后的摘要文本 */ function convertAbstract(abstractInvertedIndex) { if (!abstractInvertedIndex || typeof abstractInvertedIndex !== 'object') { return '无摘要'; } const posMap = []; for (const [word, positions] of Object.entries(abstractInvertedIndex)) { for (const pos of positions) { posMap.push({pos, word}); } } posMap.sort((a, b) => a.pos - b.pos); return posMap.map(item => item.word).join(' '); } /** * 查询 OpenAlex API 获取论文信息。 * @param {Object} params - 查询参数 * @param {string} params.keyword - 关键词 * @param {number} params.maxResults - 最大结果数 * @param {number} [params.year] - 发表年份 * @param {boolean} [params.openAccess] - 是否开放获取 * @param {string} [params.authorId] - 作者 ID * @param {string} [params.institutionId] - 机构 ID * @param {string} [params.type] - 论文类型 * @returns {Promise<Array>} - 包含论文信息的数组 */ export async function searchPapers({ keyword, maxResults, year, openAccess, authorId, institutionId, type }) { const baseUrl = 'https://api.openalex.org/works'; const params = new URLSearchParams(); params.append('search', keyword); params.append('per-page', maxResults); const filters = []; if (year) filters.push(`publication_year:${year}`); if (openAccess !== undefined) filters.push(`is_oa:${openAccess}`); if (authorId) filters.push(`authorships.author.id:${authorId}`); if (institutionId) filters.push(`institutions.id:${institutionId}`); if (type) filters.push(`type:${type}`); if (filters.length > 0) { params.append('filter', filters.join(',')); } const url = `${baseUrl}?${params.toString()}`; const response = await fetch(url); const data = await response.json(); const papers = []; const printedTitles = new Set(); for (const item of data.results) { const title = item.title || '无标题'; if (printedTitles.has(title)) continue; printedTitles.add(title); const authors = item.authorships.map(auth => auth.author.display_name || '未知作者'); const publicationYear = item.publication_year || 0; const abstractText = item.abstract_inverted_index ? convertAbstract(item.abstract_inverted_index) : '无摘要'; const link = item.id || '无链接'; const citedByCount = item.cited_by_count || 0; const pdfUrl = item.primary_location?.pdf_url || null; papers.push({ title, authors, year: publicationYear, abstractText, link, citedByCount, pdfUrl }); if (papers.length >= maxResults) break; } return papers; } /** * 根据作者名称搜索作者信息。 * @param {string} authorName - 作者名称 * @param {number} [maxResults=5] - 返回的最大结果数 * @returns {Promise<Array>} - 包含作者信息的数组 */ export async function searchAuthorsByName(authorName, maxResults = 5) { const baseUrl = 'https://api.openalex.org/authors'; const params = new URLSearchParams(); params.append('filter', `display_name.search:${authorName}`); params.append('per-page', maxResults); const url = `${baseUrl}?${params.toString()}`; const response = await fetch(url); const data = await response.json(); return data.results.map(author => ({ id: author.id, name: author.display_name, orcid: author.orcid || '无 ORCID', worksCount: author.works_count || 0, citationCount: author.cited_by_count || 0, institutions: author.last_known_institutions ? author.last_known_institutions.map(inst => ({ name: inst.display_name, country: inst.country_code, type: inst.type })) : [], hIndex: author.summary_stats?.h_index || 0, i10Index: author.summary_stats?.i10_index || 0 })); } /** * 根据机构名称获取 OpenAlex ID。 * @param {string} institutionName - 机构名称 * @returns {Promise<string|null>} - 返回机构的 OpenAlex ID 或 null */ export async function getInstitutionIdByName(institutionName) { const baseUrl = 'https://api.openalex.org/institutions'; const params = new URLSearchParams(); params.append('filter', `display_name.search:${institutionName}`); params.append('per-page', 1); const url = `${baseUrl}?${params.toString()}`; const response = await fetch(url); const data = await response.json(); if (data.results && data.results.length > 0) { return data.results[0].id; } else { return null; } } /** * 根据机构 ID 查询作品。 * @param {string} institutionId - 机构的 OpenAlex ID * @param {number} maxResults - 最大返回结果数 * @returns {Promise<Array>} - 返回作品列表 */ export async function searchWorksByInstitutionId(institutionId, maxResults = 5) { const baseUrl = 'https://api.openalex.org/works'; const params = new URLSearchParams(); params.append('filter', `authorships.institutions.id:${institutionId}`); params.append('per-page', maxResults); const url = `${baseUrl}?${params.toString()}`; const response = await fetch(url); const data = await response.json(); return data.results.map(work => ({ id: work.id, title: work.display_name, publicationYear: work.publication_year, citedByCount: work.cited_by_count, doi: work.doi, openAccess: work.open_access?.is_oa, authors: work.authorships.map(auth => auth.author.display_name) })); } /** * 根据期刊名称获取 OpenAlex ID。 * @param {string} journalName - 期刊名称 * @returns {Promise<string|null>} - 返回期刊的 OpenAlex ID 或 null */ export async function getJournalIdByName(journalName) { const baseUrl = 'https://api.openalex.org/sources'; const params = new URLSearchParams(); params.append('filter', `display_name.search:${journalName}`); params.append('per-page', 1); const url = `${baseUrl}?${params.toString()}`; const response = await fetch(url); const data = await response.json(); if (data.results && data.results.length > 0) { return data.results[0].id; } else { return null; } } /** * 根据期刊 ID 查询作品。 * @param {string} journalId - 期刊的 OpenAlex ID * @param {number} maxResults - 最大返回结果数 * @returns {Promise<Array>} - 返回作品列表 */ export async function searchWorksByJournalId(journalId, maxResults = 5) { const baseUrl = 'https://api.openalex.org/works'; const params = new URLSearchParams(); params.append('filter', `primary_location.source.id:${journalId}`); params.append('per-page', maxResults); const url = `${baseUrl}?${params.toString()}`; const response = await fetch(url); const data = await response.json(); return data.results.map(work => ({ id: work.id, title: work.display_name, publicationYear: work.publication_year, citedByCount: work.cited_by_count, doi: work.doi, openAccess: work.open_access?.is_oa, authors: work.authorships.map(auth => auth.author.display_name) })); } /** * 通过论文标题搜索论文。 * @param {string} title - 论文标题 * @param {number} [maxResults=5] - 返回的最大结果数 * @returns {Promise<Array>} - 包含论文信息的数组 */ export async function searchPapersByTitle(title, maxResults = 5) { const baseUrl = 'https://api.openalex.org/works'; const params = new URLSearchParams(); params.append('filter', `title.search:${title}`); params.append('per-page', maxResults); const url = `${baseUrl}?${params.toString()}`; const response = await fetch(url); const data = await response.json(); const papers = []; const printedTitles = new Set(); for (const item of data.results) { const paperTitle = item.title || '无标题'; if (printedTitles.has(paperTitle)) continue; printedTitles.add(paperTitle); const authors = item.authorships.map(auth => auth.author.display_name || '未知作者'); const publicationYear = item.publication_year || 0; const abstractText = item.abstract_inverted_index ? convertAbstract(item.abstract_inverted_index) : '无摘要'; const link = item.id || '无链接'; const citedByCount = item.cited_by_count || 0; const pdfUrl = item.primary_location?.pdf_url || null; const doi = item.doi || null; const journal = item.primary_location?.source?.display_name || '未知期刊'; papers.push({ title: paperTitle, authors, year: publicationYear, abstractText, link, citedByCount, pdfUrl, doi, journal }); if (papers.length >= maxResults) break; } return papers; } /** * 通过论文DOI精确查找论文。 * @param {string} doi - 论文的DOI标识符 * @returns {Promise<Object|null>} - 论文信息或null(如果未找到) */ export async function getPaperByDoi(doi) { const baseUrl = 'https://api.openalex.org/works'; const params = new URLSearchParams(); params.append('filter', `doi:${doi}`); const url = `${baseUrl}?${params.toString()}`; const response = await fetch(url); const data = await response.json(); if (!data.results || data.results.length === 0) { return null; } const item = data.results[0]; const title = item.title || '无标题'; const authors = item.authorships.map(auth => auth.author.display_name || '未知作者'); const publicationYear = item.publication_year || 0; const abstractText = item.abstract_inverted_index ? convertAbstract(item.abstract_inverted_index) : '无摘要'; const link = item.id || '无链接'; const citedByCount = item.cited_by_count || 0; const pdfUrl = item.primary_location?.pdf_url || null; const journal = item.primary_location?.source?.display_name || '未知期刊'; const isOpenAccess = item.open_access?.is_oa || false; const concepts = item.concepts?.map(concept => ({ name: concept.display_name, score: concept.score })) || []; return { title, authors, year: publicationYear, abstractText, link, citedByCount, pdfUrl, doi: doi, journal, isOpenAccess, concepts }; } /** * 进行高级论文搜索,支持多个过滤条件。 * @param {Object} params - 搜索参数 * @param {string} [params.keyword] - 关键词(可选) * @param {number} [params.minCitations] - 最小引用次数(可选) * @param {number} [params.maxCitations] - 最大引用次数(可选) * @param {string} [params.concept] - 学科概念(可选) * @param {boolean} [params.isOpenAccess] - 是否仅搜索开放获取论文(可选) * @param {number} [params.fromYear] - 起始年份(可选) * @param {number} [params.toYear] - 结束年份(可选) * @param {number} [params.maxResults=10] - 返回的最大结果数 * @returns {Promise<Array>} - 包含论文信息的数组 */ export async function advancedPaperSearch({ keyword, minCitations, maxCitations, concept, isOpenAccess, fromYear, toYear, maxResults = 10 }) { const baseUrl = 'https://api.openalex.org/works'; const params = new URLSearchParams(); // 添加关键词搜索 if (keyword) { params.append('search', keyword); } // 构建过滤条件 const filters = []; // 引用范围 if (minCitations !== undefined) { filters.push(`cited_by_count>=${minCitations}`); } if (maxCitations !== undefined) { filters.push(`cited_by_count<=${maxCitations}`); } // 学科概念 if (concept) { filters.push(`concepts.name.search:${concept}`); } // 开放获取状态 if (isOpenAccess !== undefined) { filters.push(`is_oa:${isOpenAccess}`); } // 发表年份范围 if (fromYear) { filters.push(`publication_year>=${fromYear}`); } if (toYear) { filters.push(`publication_year<=${toYear}`); } // 添加过滤条件 if (filters.length > 0) { params.append('filter', filters.join(',')); } // 设置结果数量 params.append('per-page', maxResults); const url = `${baseUrl}?${params.toString()}`; const response = await fetch(url); const data = await response.json(); return data.results.map(item => { const title = item.title || '无标题'; const authors = item.authorships.map(auth => auth.author.display_name || '未知作者'); const publicationYear = item.publication_year || 0; const abstractText = item.abstract_inverted_index ? convertAbstract(item.abstract_inverted_index) : '无摘要'; const link = item.id || '无链接'; const citedByCount = item.cited_by_count || 0; const pdfUrl = item.primary_location?.pdf_url || null; const doi = item.doi || null; const journal = item.primary_location?.source?.display_name || '未知期刊'; const isOpenAccess = item.open_access?.is_oa || false; return { title, authors, year: publicationYear, abstractText, link, citedByCount, pdfUrl, doi, journal, isOpenAccess }; }); } /** * 使用有道翻译API将中文查询参数翻译成英文 * @param {string} text - 要翻译的文本 * @param {string} appKey - 有道智云应用ID * @param {string} appSecret - 有道智云应用密钥 * @returns {Promise<string>} - 翻译后的英文文本 */ export async function translateQueryToEnglish(text, appKey, appSecret) { // 如果文本是空的,直接返回 if (!text || text.trim() === '') { return text; } // 检测文本是否包含中文字符 const hasChinese = /[\u4e00-\u9fa5]/.test(text); if (!hasChinese) { return text; // 如果不包含中文,直接返回原文 } try { // 导入Node.js内置的crypto模块 const crypto = await import('crypto'); // 生成签名 const salt = new Date().getTime(); const curtime = Math.round(new Date().getTime() / 1000); const input = text.length <= 20 ? text : text.substring(0, 10) + text.length + text.substring(text.length - 10); // 使用Node.js内置crypto模块的createHash方法 const sign = crypto.createHash('sha256') .update(appKey + input + salt + curtime + appSecret) .digest('hex'); const url = 'https://openapi.youdao.com/api'; const params = new URLSearchParams(); params.append('q', text); params.append('from', 'zh-CHS'); // 源语言:中文 params.append('to', 'en'); // 目标语言:英文 params.append('appKey', appKey); params.append('salt', salt); params.append('sign', sign); params.append('signType', 'v3'); params.append('curtime', curtime); const response = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: params }); const data = await response.json(); if (data.errorCode === '0') { return data.translation[0]; } else { console.error('参数翻译错误:', data.errorCode); return text; // 翻译失败时返回原文 } } catch (error) { console.error('参数翻译请求失败:', error); return text; // 请求失败时返回原文 } }