Files
xiaoli-cli/operit_py/crossref.py
T
shuiyu1123 dc77dfd304 feat: 将 47 个 Operit TypeScript 脚本全面翻译为 Python
- 新增 operit_runtime.py: 跨平台 Python 运行时
  - 实现 Tools.System/Files/Net/UI/Memory/FFmpeg/Workflow/Chat/SoftwareSettings/Tasker
  - 实现 OkHttp 链式 HTTP 客户端兼容层
  - 实现 complete()/exports/console 全局对象
  - 实现 OperitScriptLoader 脚本加载器
  - 支持 Windows/macOS/Linux 三平台

- 新增 operit_py/ 目录: 47 个 Python 翻译脚本
  - 搜索类: duckduckgo, google_search, tavily, zhipu_search, various_search, crossref
  - 绘图类: openai_draw, qwen_draw, xai_draw, minimax_draw, siliconflow_draw, zhipu_draw, nanobanana_draw
  - 媒体类: ffmpeg, file_converter, douyin_download, bilibili_tools, jmcomic
  - 文件类: extended_file_tools, hex_editor, reader, beeimg_image_uploader
  - 网络类: extended_http_tools, network_test
  - 自动化类: browser, automatic_ui_base, automatic_ui_subagent, automatic_bilibili_assistant, automatic_xiaohongshu_assistant, automatic_baidu_map_assistant
  - 系统类: system_tools, super_admin, daily_life, operit_editor
  - 对话类: ai_chat, extended_chat, qq_intelligent
  - 工作流类: workflow, vflow_trigger, tasker
  - 工具类: time, code_runner, quick_start, extended_memory_tools, baidu_map, 12306, pdf_vision_parser

- 测试结果: 47/47 语法检查通过, 38 脚本加载成功, 280 个工具注册
- Android 专属功能已降级处理(保留接口,返回不支持提示)
2026-06-23 12:27:18 +00:00

516 lines
18 KiB
Python

# METADATA
# {
# "name": "crossref",
# "display_name": {
# "zh": "Crossref 学术文献查询",
# "en": "Crossref Academic Literature Search"
# },
# "description": {
# "zh": "Crossref 学术文献查询工具,提供 DOI 查询、关键词搜索、作者搜索等功能,帮助用户查找和获取学术文章元数据。",
# "en": "Crossref scholarly literature search tools: query by DOI, keyword, author, title, ISSN, and retrieve publication metadata."
# },
# "category": "Search",
# "enabledByDefault": true,
# "tools": [
# {
# "name": "search_by_doi",
# "description": { "zh": "通过 DOI (Digital Object Identifier) 查询文章的详细信息", "en": "Query article details by DOI (Digital Object Identifier)." },
# "parameters": [
# { "name": "doi", "description": { "zh": "文章的 DOI 标识符,例如 '10.1038/nature12373'", "en": "DOI identifier, e.g. '10.1038/nature12373'" }, "type": "string", "required": true }
# ]
# },
# {
# "name": "search_by_keyword",
# "description": { "zh": "通过关键词搜索学术文章", "en": "Search scholarly articles by keyword." },
# "parameters": [
# { "name": "query", "description": { "zh": "搜索关键词", "en": "Search query keyword(s)" }, "type": "string", "required": true },
# { "name": "rows", "description": { "zh": "返回结果数量,默认 10,最大 100", "en": "Number of results to return (default: 10, max: 100)" }, "type": "number", "required": false },
# { "name": "sort", "description": { "zh": "排序方式,可选值:'relevance'(相关性), 'score'(评分), 'updated'(更新时间), 'deposited'(提交时间), 'indexed'(索引时间), 'published'(发布时间)", "en": "Sort mode. Options: 'relevance', 'score', 'updated', 'deposited', 'indexed', 'published'." }, "type": "string", "required": false },
# { "name": "order", "description": { "zh": "排序顺序,可选值:'asc'(升序), 'desc'(降序),默认 'desc'", "en": "Sort order: 'asc' or 'desc' (default: 'desc')." }, "type": "string", "required": false }
# ]
# },
# {
# "name": "search_by_author",
# "description": { "zh": "通过作者名字搜索文章", "en": "Search articles by author name." },
# "parameters": [
# { "name": "author", "description": { "zh": "作者名字", "en": "Author name" }, "type": "string", "required": true },
# { "name": "rows", "description": { "zh": "返回结果数量,默认 10,最大 100", "en": "Number of results to return (default 10, max 100)" }, "type": "number", "required": false }
# ]
# },
# {
# "name": "search_by_title",
# "description": { "zh": "通过文章标题搜索", "en": "Search articles by title." },
# "parameters": [
# { "name": "title", "description": { "zh": "文章标题或标题关键词", "en": "Article title or title keyword(s)" }, "type": "string", "required": true },
# { "name": "rows", "description": { "zh": "返回结果数量,默认 10,最大 100", "en": "Number of results to return (default 10, max 100)" }, "type": "number", "required": false }
# ]
# },
# {
# "name": "search_by_issn",
# "description": { "zh": "通过期刊 ISSN 查询该期刊发表的文章", "en": "Search articles published in a journal by ISSN." },
# "parameters": [
# { "name": "issn", "description": { "zh": "期刊的 ISSN 标识符,例如 '1476-4687'", "en": "Journal ISSN identifier, e.g. '1476-4687'" }, "type": "string", "required": true },
# { "name": "rows", "description": { "zh": "返回结果数量,默认 10,最大 100", "en": "Number of results to return (default 10, max 100)" }, "type": "number", "required": false }
# ]
# }
# ]
# }
import json
import re
from urllib.parse import quote
BASE_URL = "https://api.crossref.org"
DEFAULT_ROWS = 10
MAX_ROWS = 100
def buildQueryString(params):
return "&".join(f"{quote(str(key))}={quote(str(value))}" for key, value in params.items())
# 格式化作者信息
def formatAuthors(authors):
if not authors or len(authors) == 0:
return "N/A"
names = []
for author in authors[:5]: # 只显示前5个作者
given = author.get("given") or ""
family = author.get("family") or ""
name = f"{given} {family}".strip()
if len(name) > 0:
names.append(name)
return ", ".join(names)
# 格式化日期
def formatDate(dateParts):
if not dateParts or len(dateParts) == 0 or not dateParts[0]:
return "N/A"
parts = dateParts[0]
if len(parts) == 1:
return f"{parts[0]}"
if len(parts) == 2:
return f"{parts[0]}-{str(parts[1]).rjust(2, '0')}"
if len(parts) == 3:
return f"{parts[0]}-{str(parts[1]).rjust(2, '0')}-{str(parts[2]).rjust(2, '0')}"
return "N/A"
def _get(obj, key, default=None):
if isinstance(obj, dict):
return obj.get(key, default)
return default
# 格式化单篇文章信息
def formatArticle(item, index=None):
lines = []
if index is not None:
lines.append(f"\n=== Article {index + 1} ===")
# 标题
title = (item.get("title")[0] if item.get("title") and len(item.get("title")) > 0 else "No Title")
lines.append(f"Title: {title}")
# DOI
if item.get("DOI"):
lines.append(f"DOI: {item['DOI']}")
lines.append(f"URL: https://doi.org/{item['DOI']}")
# 作者
authors = formatAuthors(item.get("author"))
lines.append(f"Authors: {authors}")
# 发表日期
published = _get(item, "published") or {}
publishedPrint = _get(item, "published-print") or {}
publishedOnline = _get(item, "published-online") or {}
publishedDate = formatDate(
_get(published, "date-parts") or _get(publishedPrint, "date-parts") or _get(publishedOnline, "date-parts")
)
lines.append(f"Published: {publishedDate}")
# 期刊/会议
containerTitle = item.get("container-title")
if containerTitle and len(containerTitle) > 0:
lines.append(f"Journal/Conference: {containerTitle[0]}")
# ISSN
issn = item.get("ISSN")
if issn and len(issn) > 0:
lines.append(f"ISSN: {', '.join(issn)}")
# 出版商
if item.get("publisher"):
lines.append(f"Publisher: {item['publisher']}")
# 类型
if item.get("type"):
lines.append(f"Type: {item['type']}")
# 引用次数
if item.get("is-referenced-by-count") is not None:
lines.append(f"Citations: {item['is-referenced-by-count']}")
# 摘要(如果有)
if item.get("abstract"):
# 移除 HTML 标签
abstractText = re.sub(r"<[^>]*>", "", item["abstract"])
lines.append(f"Abstract: {abstractText[:500]}{'...' if len(abstractText) > 500 else ''}")
return "\n".join(lines)
async def _httpGet(url, headers):
client = OkHttp.newBuilder().build()
response = await client.newRequest().url(url).method("GET").headers(headers).build().execute()
return response
# 通过 DOI 查询文章
async def searchByDoi(params):
doi = params.get("doi")
if not doi or doi.strip() == "":
return {
"success": False,
"message": "请提供有效的 DOI"
}
try:
url = f"{BASE_URL}/works/{quote(doi, safe='')}"
response = await _httpGet(url, {
"User-Agent": "Operit/1.0 (mailto:support@example.com)"
})
if not response.isSuccessful():
return {
"success": False,
"message": f"查询失败: HTTP {response.statusCode} - {getattr(response, 'statusMessage', '')}"
}
data = json.loads(response.content)
if data.get("status") == "ok" and data.get("message"):
article = formatArticle(data["message"])
return {
"success": True,
"message": "查询成功",
"data": article
}
else:
return {
"success": False,
"message": "未找到该 DOI 对应的文章"
}
except Exception as error:
return {
"success": False,
"message": f"查询失败: {str(error)}"
}
# 通过关键词搜索文章
async def searchByKeyword(params):
query = params.get("query")
rows = params.get("rows") if params.get("rows") is not None else DEFAULT_ROWS
sort = params.get("sort") if params.get("sort") is not None else "relevance"
order = params.get("order") if params.get("order") is not None else "desc"
if not query or query.strip() == "":
return {
"success": False,
"message": "请提供有效的搜索关键词"
}
actualRows = min(max(int(rows), 1), MAX_ROWS)
try:
queryString = buildQueryString({
"query": query,
"rows": str(actualRows),
"sort": sort,
"order": order
})
url = f"{BASE_URL}/works?{queryString}"
response = await _httpGet(url, {
"User-Agent": "Operit/1.0 (mailto:support@example.com)"
})
if not response.isSuccessful():
return {
"success": False,
"message": f"搜索失败: HTTP {response.statusCode} - {getattr(response, 'statusMessage', '')}"
}
data = json.loads(response.content)
if data.get("status") == "ok" and data.get("message") and data["message"].get("items"):
items = data["message"]["items"]
totalResults = data["message"].get("total-results")
results = [formatArticle(item, index) for index, item in enumerate(items)]
summary = f"Found {totalResults} results (showing {len(items)}):\n" + "\n\n".join(results)
return {
"success": True,
"message": "搜索成功",
"data": summary,
"total": totalResults,
"count": len(items)
}
else:
return {
"success": False,
"message": "未找到相关文章"
}
except Exception as error:
return {
"success": False,
"message": f"搜索失败: {str(error)}"
}
# 通过作者搜索文章
async def searchByAuthor(params):
author = params.get("author")
rows = params.get("rows") if params.get("rows") is not None else DEFAULT_ROWS
if not author or author.strip() == "":
return {
"success": False,
"message": "请提供有效的作者名字"
}
actualRows = min(max(int(rows), 1), MAX_ROWS)
try:
queryString = buildQueryString({
"query.author": author,
"rows": str(actualRows)
})
url = f"{BASE_URL}/works?{queryString}"
response = await _httpGet(url, {
"User-Agent": "Operit/1.0 (mailto:support@example.com)"
})
if not response.isSuccessful():
return {
"success": False,
"message": f"搜索失败: HTTP {response.statusCode} - {getattr(response, 'statusMessage', '')}"
}
data = json.loads(response.content)
if data.get("status") == "ok" and data.get("message") and data["message"].get("items"):
items = data["message"]["items"]
totalResults = data["message"].get("total-results")
results = [formatArticle(item, index) for index, item in enumerate(items)]
summary = f"Found {totalResults} results for author \"{author}\" (showing {len(items)}):\n" + "\n\n".join(results)
return {
"success": True,
"message": "搜索成功",
"data": summary,
"total": totalResults,
"count": len(items)
}
else:
return {
"success": False,
"message": f"未找到该作者 \"{author}\" 的文章"
}
except Exception as error:
return {
"success": False,
"message": f"搜索失败: {str(error)}"
}
# 通过标题搜索文章
async def searchByTitle(params):
title = params.get("title")
rows = params.get("rows") if params.get("rows") is not None else DEFAULT_ROWS
if not title or title.strip() == "":
return {
"success": False,
"message": "请提供有效的文章标题"
}
actualRows = min(max(int(rows), 1), MAX_ROWS)
try:
queryString = buildQueryString({
"query.title": title,
"rows": str(actualRows)
})
url = f"{BASE_URL}/works?{queryString}"
response = await _httpGet(url, {
"User-Agent": "Operit/1.0 (mailto:support@example.com)"
})
data = json.loads(response.content)
if data.get("status") == "ok" and data.get("message") and data["message"].get("items"):
items = data["message"]["items"]
totalResults = data["message"].get("total-results")
results = [formatArticle(item, index) for index, item in enumerate(items)]
summary = f"Found {totalResults} results matching title \"{title}\" (showing {len(items)}):\n" + "\n\n".join(results)
return {
"success": True,
"message": "搜索成功",
"data": summary,
"total": totalResults,
"count": len(items)
}
else:
return {
"success": False,
"message": f"未找到标题包含 \"{title}\" 的文章"
}
except Exception as error:
return {
"success": False,
"message": f"搜索失败: {str(error)}"
}
# 通过 ISSN 查询期刊文章
async def searchByISSN(params):
issn = params.get("issn")
rows = params.get("rows") if params.get("rows") is not None else DEFAULT_ROWS
if not issn or issn.strip() == "":
return {
"success": False,
"message": "请提供有效的 ISSN"
}
actualRows = min(max(int(rows), 1), MAX_ROWS)
try:
url = f"{BASE_URL}/journals/{quote(issn, safe='')}/works?rows={actualRows}"
response = await _httpGet(url, {
"User-Agent": "Operit/1.0 (mailto:support@example.com)"
})
if not response.isSuccessful():
return {
"success": False,
"message": f"查询失败: HTTP {response.statusCode} - {getattr(response, 'statusMessage', '')}"
}
data = json.loads(response.content)
if data.get("status") == "ok" and data.get("message") and data["message"].get("items"):
items = data["message"]["items"]
totalResults = data["message"].get("total-results")
results = [formatArticle(item, index) for index, item in enumerate(items)]
summary = f"Found {totalResults} articles from journal ISSN {issn} (showing {len(items)}):\n" + "\n\n".join(results)
return {
"success": True,
"message": "查询成功",
"data": summary,
"total": totalResults,
"count": len(items)
}
else:
return {
"success": False,
"message": f"未找到 ISSN \"{issn}\" 对应期刊的文章"
}
except Exception as error:
return {
"success": False,
"message": f"查询失败: {str(error)}"
}
# 包装函数,统一处理错误
async def wrapToolExecution(func, params):
try:
result = await func(params)
complete(result)
except Exception as error:
console.error("工具执行失败", error)
complete({
"success": False,
"message": f"工具执行时发生意外错误: {str(error)}",
})
# 测试函数
async def main(params=None):
if params is None:
params = {}
console.log("=== Crossref API 测试 ===\n")
# 测试 1: 通过 DOI 查询
console.log("1. 测试通过 DOI 查询...")
doiResult = await searchByDoi({"doi": "10.1038/nature12373"})
console.log(json.dumps(doiResult, ensure_ascii=False, indent=2))
console.log("\n")
# 测试 2: 通过关键词搜索
console.log("2. 测试通过关键词搜索...")
keywordResult = await searchByKeyword({"query": "machine learning", "rows": 3})
console.log(json.dumps(keywordResult, ensure_ascii=False, indent=2))
console.log("\n")
# 测试 3: 通过作者搜索
console.log("3. 测试通过作者搜索...")
authorResult = await searchByAuthor({"author": "John Smith", "rows": 3})
console.log(json.dumps(authorResult, ensure_ascii=False, indent=2))
console.log("\n")
# 测试 4: 通过标题搜索
console.log("4. 测试通过标题搜索...")
titleResult = await searchByTitle({"title": "neural networks", "rows": 3})
console.log(json.dumps(titleResult, ensure_ascii=False, indent=2))
console.log("\n")
console.log("=== 测试完成 ===")
async def _exported_search_by_doi(params):
await wrapToolExecution(searchByDoi, params)
async def _exported_search_by_keyword(params):
await wrapToolExecution(searchByKeyword, params)
async def _exported_search_by_author(params):
await wrapToolExecution(searchByAuthor, params)
async def _exported_search_by_title(params):
await wrapToolExecution(searchByTitle, params)
async def _exported_search_by_issn(params):
await wrapToolExecution(searchByISSN, params)
exports.search_by_doi = _exported_search_by_doi
exports.search_by_keyword = _exported_search_by_keyword
exports.search_by_author = _exported_search_by_author
exports.search_by_title = _exported_search_by_title
exports.search_by_issn = _exported_search_by_issn
exports.main = main