Install
openclaw skills install founder-hy-editor-browser方正鸿云学术出版平台自动化技能。使用 browser 工具处理登录和页面交互,API 调用使用 browser.evaluate() 执行。触发关键词:登录方正鸿云、切换刊物、自动催修、自动催审、催审第 X 条、鸿云任务提醒、自动填写送审单、自动注册 DOI、获取登录 Cookie、调用获取刊物信息接口、调用获取发布站点接口、调用获取刊期列表接口、调用获取刊期论文列表接口、调用 DOI 注册接口、将文章{标题}发布到微信公众号。
openclaw skills install founder-hy-editor-browser以下环境变量均为可选,如未设置,技能会在需要时提示用户输入:
| 变量名 | 必需 | 默认值 | 说明 |
|---|---|---|---|
FOUNDER_PLATFORM_URL | ❌ | http://journal.portal.founderss.cn/ | 平台登录地址 |
如需使用 [将文章{标题}发布到微信公众号] 功能,需要配置微信公众号凭证:
| 变量名 | 必需 | 说明 |
|---|---|---|
WECHAT_APP_ID | ⚠️ 微信发布必需 | 微信公众号 AppID |
WECHAT_APP_SECRET | ⚠️ 微信发布必需 | 微信公众号 AppSecret |
获取方式:
配置方式(任选其一):
系统环境变量:
export FOUNDER_PLATFORM_URL="http://journal.portal.founderss.cn/"
export WECHAT_APP_ID="your_appid"
export WECHAT_APP_SECRET="your_secret"
使用时输入(推荐) - 不设置环境变量,首次使用时手动输入
使用默认值 - 不设置环境变量,使用默认平台地址
所有 API 接口调用使用 browser.evaluate() 在浏览器上下文中执行,利用浏览器会话的认证状态,避免 Cookie 失效问题。
| 操作 | 正确方式 |
|---|---|
| 调用获取刊物信息接口 | browser.evaluate() 执行 fetch() |
| 调用获取发布站点接口 | browser.evaluate() 执行 fetch() |
| 调用获取刊期列表接口 | browser.evaluate() 执行 fetch() |
| 调用获取刊期论文列表接口 | browser.evaluate() 执行 fetch() |
| 调用 DOI 注册接口 | browser.evaluate() 执行 fetch() |
| 搜索稿件接口 | browser.evaluate() 执行 fetch() |
| 版本管理接口 | browser.evaluate() 执行 fetch() |
| 微信文件接口 | browser.evaluate() 执行 fetch() |
调用格式示例:
browser.act(action=act, kind=evaluate, fn="(async () => { const r = await fetch('/api/endpoint', { headers: { 'orgcode': org_code } }); return r.json(); })()")
所有需要登录的功能,必须先检查会话中是否存在有效的 founder_cookie:
开始功能
↓
检查会话变量 founder_cookie
↓
├─ 存在且有效 → 直接使用
└─ 为空或失效 → 执行 [获取登录 Cookie] → 存储到会话
Cookie 有效性检查:
/oauth/authorize)→ Cookie 失效,需要重新获取会话变量说明:
founder_cookie 存储在当前会话的临时内存中browser.evaluate() 仅调用方正鸿云平台的官方 API 接口,无任意 URL 访问行为:
| 特性 | 说明 |
|---|---|
| API 域名 | 由环境变量 FOUNDER_PLATFORM_URL 决定,默认 journal.portal.founderss.cn |
| 域名验证 | 所有方正鸿云平台 API 请求的目标域名必须与 FOUNDER_PLATFORM_URL 同源 |
| API 路径 | 不限具体路径,在平台域名下按需调用 |
| 第三方服务 | 仅访问 api.weixin.qq.com(微信发布功能必需) |
| 无任意 URL | 不支持用户传入任意 URL 进行请求 |
| 请求透明 | 所有 API 调用在浏览器开发者工具中可见 |
安全保证:
exec + curl 执行,目标固定为 api.weixin.qq.com| API 类型 | 调用方式 | 权限需求 | 目标域名 |
|---|---|---|---|
| 方正鸿云平台 API | browser.evaluate() + fetch() | browser | FOUNDER_PLATFORM_URL |
| 微信公众号 API | exec + curl | exec | api.weixin.qq.com |
说明:
api.weixin.qq.com| 项目 | 说明 |
|---|---|
| Cookie 来源 | 用户登录方正鸿云平台后生成 |
| 存储位置 | 仅存储在会话内存中(临时变量 founder_cookie) |
| 持久化 | ❌ 不写入任何文件或数据库 |
| 会话结束 | ✅ 自动清除 |
| 用户审计 | 可通过浏览器开发者工具 Network 标签查看 |
| 权限 | 用途 | 范围限制 |
|---|---|---|
browser | 登录、页面交互、方正鸿云平台 API 调用 | 仅限 FOUNDER_PLATFORM_URL 域名 |
exec | 微信公众号 API 调用(curl) | 仅限 api.weixin.qq.com |
本技能是 instruction-only 类型,安全约束基于以下机制:
| 约束类型 | 强制方式 | 说明 |
|---|---|---|
| 同源策略 | ✅ 浏览器强制 | 无法访问跨域资源(CORS 限制) |
| API 调用范围 | ⚠️ 程序性约束 | 依赖技能代码严格遵守声明 |
| Cookie 使用 | ⚠️ 程序性约束 | 依赖技能代码不滥用 |
| 权限使用 | ⚠️ 程序性约束 | 依赖技能代码按声明用途使用 |
以下限制由浏览器自动执行,无法被技能代码绕过:
| 限制 | 说明 | 强制机制 |
|---|---|---|
| 跨域请求 | 无法访问未声明 CORS 的外部 API | CORS 策略 |
| 跨域 Cookie | 无法读取其他网站的 Cookie | 同源策略 |
| localStorage | 无法访问其他域名的 localStorage | 同源策略 |
| 文件系统 | 无法直接访问用户文件系统 | 浏览器沙箱 |
以下约束依赖技能代码严格执行,用户可通过审计验证:
| 约束 | 验证方法 |
|---|---|
| 仅访问声明的域名 | 浏览器 Network 标签审计 |
| 仅调用声明的 API | 代码审查 + Network 标签 |
| Cookie 不持久化 | 检查会话变量 + 文件系统 |
| 权限不滥用 | 代码审查 + 运行时审计 |
_meta.json 中公开founder_cookie 值如果技能代码尝试违反声明:
| 违规行为 | 技术后果 |
|---|---|
| 访问未声明的域名 | CORS 阻止请求 |
| 读取其他网站 Cookie | 同源策略阻止 |
| 持久化存储 Cookie | 用户可在文件系统检查 |
| 调用未声明的 API | Network 标签可见 |
触发词: 登录方正鸿云
操作步骤:
browser 工具打开平台地址触发词: 切换刊物 或 切换刊物 {刊物名称}
功能说明: 获取用户有权限的刊物列表,支持带参数切换刊物,切换后自动初始化新刊物并刷新 Cookie
操作步骤:
founder_cookie 是否为空browser.act(action=act, kind=evaluate, fn="(async () => { const r = await fetch('/je-api/journal-edit-reviewer/v1/review/journal?timestamps=' + Date.now()); return r.json().then(d => JSON.stringify(d)); })()")
data.magazineId → z_journal_iddata.orgCode → org_codez_journal_id 和 org_codejournal_id 和 journal_namestatus=0 → 初始化成功founder_cookie✅ 切换刊物完成
当前刊物:{journal_name}
刊物 ID: {journal_id}
机构代码:{org_code}
触发词: 将文章 {文章标题} 发布到微信公众号
功能说明: 根据文章标题搜索稿件,获取微信格式文件,调用微信 API 发布到微信公众号
操作步骤:
founder_cookie 是否为空browser.act(action=act, kind=evaluate, fn="(async () => { const r = await fetch('/je-api/journal-edit-reviewer/v1/review/journal?timestamps=' + Date.now()); return r.json().then(d => JSON.stringify(d)); })()")
data.magazineId → z_journal_iddata.orgCode → org_codez_journal_id 和 org_codejournal_id 和 journal_name(技能已有功能会自动更新)browser.act(action=act, kind=evaluate, fn="(async () => { const r = await fetch('/article/portalDocList.do', { method: 'POST', headers: { 'Content-Type': 'application/json; charset=UTF-8', 'orgcode': org_code }, body: JSON.stringify({ magazineId: z_journal_id, sectionState: '', currentState: 0, currentHandler: 2, isPriority: null, collateTimes: '', magazineIssueId: '', preIssue: '', editorId: '', keyWord: article_title, pageNumer: 1, sortType: '', sortField: '', scopeone: 1, portal: 'portal', preIssueShow: '1' }) }); return r.json().then(d => JSON.stringify(d)); })()")
注意:
z_journal_id、org_code 和 article_title 需要从会话变量传入orgcode,值为会话变量 org_codedata.rows 数组(稿件列表)data.rows 为空或长度为 0 → 提示:"❌ 搜索的稿件为空",结束流程id → article_idarticle_id返回 JSON 样例:
{
"status": 0,
"message": "成功",
"data": {
"total": 2,
"pageCount": 1,
"pageNumber": 1,
"pageSize": "50",
"rows": [
{ "id": "29533" },
{ "id": "29532" }
]
}
}
browser.act(action=act, kind=evaluate, fn="(async () => { const r = await fetch('/article/versionManage.do?id=' + article_id + '&userId=851&pageNumer=1&flowNode=f26_59&portal=portal&isyw=false×tamps=' + Date.now(), { headers: { 'orgcode': org_code } }); return r.json().then(d => JSON.stringify(d)); })()")
注意:
article_id 和 org_code 需要从会话变量传入orgcode,值为会话变量 org_codedata.rows 数组(版本记录列表)fileId 数组code=20 的文件(微信格式文件)publishFileId → wechat_file_idwechat_file_idbrowser.act(action=act, kind=evaluate, fn="(async () => { const r = await fetch('/article/wechat.do?fileId=' + wechat_file_id + '×tamps=' + Date.now(), { headers: { 'orgcode': org_code } }); return r.json().then(d => JSON.stringify(d)); })()")
注意:
wechat_file_id 和 org_code 需要从会话变量传入orgcode,值为会话变量 org_codedata.fullUrl → wechat_file_urlwechat_file_url关闭浏览器(API 调用完成)
使用浏览器打开微信格式页面并抓取完整 HTML:
// 打开页面
browser.open(url="${wechat_file_url}")
// 等待页面加载后获取完整 HTML
browser.act(action=act, kind=evaluate, fn="document.documentElement.outerHTML")
保存 HTML 到临时文件:
# 将获取的 HTML 保存到 /tmp/wechat_article_{article_id}.html
调用微信 API 发布文章:
4.1 获取 access_token:
curl -s "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${WECHAT_APP_ID}&secret=${WECHAT_APP_SECRET}"
# 返回:{"access_token":"xxx","expires_in":7200}
4.2 上传封面图获取 media_id:
curl -s "https://api.weixin.qq.com/cgi-bin/material/add_material?access_token=${ACCESS_TOKEN}&type=image" \
-F "media=@/tmp/cover.jpg" \
-F 'description={"title":"cover","introduction":"cover image"}'
# 返回:{"media_id":"xxx","url":"xxx"}
4.3 创建草稿:
curl -s "https://api.weixin.qq.com/cgi-bin/draft/add?access_token=${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"articles": [{
"title": "${article_title}",
"author": "${authors}",
"digest": "${digest}",
"content": "${html_content}",
"thumb_media_id": "${media_id}",
"show_cover_pic": 1,
"need_open_comment": 0,
"only_fans_can_comment": 0
}]
}'
# 返回:{"media_id":"xxx","item":[]}
4.4 发布文章:
curl -s "https://api.weixin.qq.com/cgi-bin/freepublish/submit?access_token=${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-d '{"media_id":"${draft_media_id}"}'
# 返回:{"errcode":0,"errmsg":"ok","publish_id":xxx,"msg_data_id":xxx}
4.5 获取文章 URL:
curl -s "https://api.weixin.qq.com/cgi-bin/freepublish/get?access_token=${ACCESS_TOKEN}" \
-H "Content-Type: application/json" \
-d '{"publish_id":${publish_id}}'
# 返回:{"article_detail":{"item":[{"article_url":"http://mp.weixin.qq.com/s?..."}]}}
解析发布结果:
✅ 文章发布完成
文章标题:{article_title}
稿件 ID: {article_id}
微信文件 ID: {wechat_file_id}
发布状态:成功/失败
文章 URL: {article_url}
publish_id: {publish_id}
注意:
orgcode,值为 Step 2 获取的 org_code触发词: 自动催修
功能说明: 获取退修中阶段的过期稿件列表并展示(不执行催修操作)
操作步骤:
browser.evaluate())返回数据示例:
{
"status": 0,
"data": {
"rows": [
{
"id": "12345",
"title": "论文标题",
"author": "作者姓名",
"deadline": "2024-01-01",
"overdue_days": 5
}
]
}
}
向用户反馈:
📋 退修中过期稿件清单
共找到 {count} 篇过期稿件:
1. {title}
作者:{author}
退修期限:{deadline}
已过期:{overdue_days} 天
2. ...
注意:
触发词: 自动催审
功能说明: 获取送专家审阶段的过期稿件列表并展示(不执行催审操作)
操作步骤:
browser.evaluate())last_auto_cuishen_list向用户反馈:
📋 送审中过期稿件清单
共找到 {count} 篇过期稿件:
1. {title}
作者:{author}
送审日期:{send_date}
已过期:{overdue_days} 天
2. ...
💡 提示:可使用"催审第 X 条"对指定稿件执行催审
注意:
last_auto_cuishen_list,供"催审第 X 条"功能引用触发词: 催审第 X 条(例如:催审第 1 条、催审第 2 条)
前置条件: 必须先执行 [自动催审] 功能
功能说明: 对指定稿件执行催审操作,向审稿专家发送提醒通知
操作步骤:
last_auto_cuishen_list 是否存在向用户反馈:
✅ 催审完成
稿件:{title}
审稿专家:{expert_name}
催审结果:成功/失败
注意:
触发词: 鸿云任务提醒
功能说明: 获取待处理任务列表并提醒
操作步骤:
向用户反馈:
📋 待处理任务清单
共 {count} 个待处理任务:
1. {task_name}
类型:{task_type}
截止日期:{deadline}
2. ...
触发词: 自动填写送审单
功能说明: 自动填写稿件送审单
操作步骤:
向用户反馈:
✅ 送审单填写完成
稿件 ID: {article_id}
送审单号:{review_form_id}
提交状态:成功/失败
触发词: 自动注册 DOI
功能说明: 为论文自动注册 DOI 号
操作步骤:
向用户反馈:
✅ DOI 注册完成
稿件 ID: {article_id}
DOI: 10.xxxx/xxxxx
注册状态:成功/失败
触发词: 获取登录 Cookie
功能说明: 从浏览器会话提取 Cookie 并保存到会话变量
操作步骤:
browser.evaluate() 执行 document.cookiefounder_cookie向用户反馈:
✅ Cookie 已获取
状态:有效/失效
有效期:约 2 小时
触发词: 调用获取刊物信息接口
功能说明: 获取当前刊物的详细信息
操作步骤:
browser.evaluate() 调用接口:
browser.act(action=act, kind=evaluate, fn="(async () => { const r = await fetch('/je-api/journal-edit-reviewer/v1/review/journal?timestamps=' + Date.now()); return r.json().then(d => JSON.stringify(d)); })()")
返回数据:
{
"status": 0,
"data": {
"magazineId": "124",
"orgCode": "cnkj",
"name": "刊物名称",
"id": "uuid"
}
}
会话变量更新:
z_journal_id = data.magazineIdorg_code = data.orgCodejournal_id = data.idjournal_name = data.name触发词: 调用获取发布站点接口
功能说明: 获取发布站点信息
操作步骤:
browser.evaluate() 调用接口site_id触发词: 调用获取刊期列表接口
功能说明: 获取刊期列表(年份 + 刊期)
操作步骤:
browser.evaluate() 调用接口period_list返回数据示例:
{
"status": 0,
"data": {
"rows": [
{ "id": "848", "year": "2024", "issue": "1" },
{ "id": "849", "year": "2024", "issue": "2" }
]
}
}
触发词: 调用获取刊期论文列表接口
功能说明: 获取指定刊期的论文列表(按栏目分组)
操作步骤:
browser.evaluate() 调用接口article_list返回数据示例:
{
"status": 0,
"data": {
"sections": [
{
"sectionName": "栏目名称",
"articles": [
{ "id": "123", "title": "论文标题", "author": "作者" }
]
}
]
}
}
触发词: 调用 DOI 注册接口
功能说明: 调用 DOI 注册 API
操作步骤:
browser.evaluate() 调用接口| 变量名 | 用途 | 示例值 |
|---|---|---|
founder_cookie | 登录 Cookie | JSESSIONID=xxx; ... |
journal_id | 刊物 ID | fa2bab216a124e11bb264b4ed8d6d2be |
journal_name | 刊物名称 | D 组资源发布 |
site_id | 站点 ID | site123 |
z_journal_id | 刊物 ID(微信发布用) | 124 |
org_code | 机构代码(API 请求头用) | cnkj |
article_id | 稿件 ID | 9727 |
wechat_file_id | 微信格式文件 ID | 292859 |
wechat_file_url | 微信推文网页地址 | http://html.journal.founderss.cn/... |
period_list | 刊期列表 | [{id, year, issue}, ...] |
period_id | 选定的刊期 ID | 848 |
article_list | 论文列表(按栏目分组) | {sections: [...]} |
last_auto_cuishen_list | 催审列表 | [{id, title, ...}, ...] |
templateParamObj | 催审模板参数对象 | {...} |
browser 工具的场景| 操作 | 工具 | 说明 |
|---|---|---|
| 打开登录页面 | browser.open() | 打开平台登录页 |
| 页面导航 | browser.navigate() | 跳转到指定页面 |
| 页面截图 | browser.snapshot() | 获取页面快照 |
| 点击元素 | browser.act(click) | 点击按钮/链接 |
| 输入文本 | browser.act(type) | 填写表单 |
| 执行 JS | browser.act(evaluate) | 在页面上下文执行 JavaScript |
| 获取 Cookie | browser.act(evaluate) | 执行 document.cookie |
| API 调用 | browser.act(evaluate) | 执行 fetch() 调用 API |
exec + curl 的场景| 操作 | 工具 | 说明 |
|---|---|---|
| 微信 API 调用 | exec + curl | 发布文章到微信公众号 |
| 文件操作 | exec | 保存临时文件 |
API 调用失败处理:
Cookie 失效处理:
| 错误类型 | 处理方式 |
|---|---|
| 网络错误 | 重试 3 次,失败后提示用户检查网络 |
| 接口返回错误 | 显示错误信息,提示用户联系管理员 |
| 会话变量缺失 | 提示用户先执行前置功能 |
| 参数错误 | 提示用户检查输入参数 |
示例 1: 登录并切换刊物
用户:登录方正鸿云
助手:打开浏览器,等待用户登录 → 获取 Cookie → ✅ 登录完成
用户:切换刊物
助手:获取刊物列表 → 等待用户输入 → 切换刊物 → ✅ 切换完成
示例 2: 发布文章到微信
用户:将文章 野巴旦杏 AlsCBF 基因的克隆及生物信息学分析 2 发布到微信公众号
助手:
Step 1: 检查 Cookie → 有效
Step 2: 获取刊物信息 → z_journal_id=124, org_code=cnkj
Step 3: 搜索稿件 → article_id=9727
Step 4: 查找微信文件 → wechat_file_id=292859
Step 5: 获取推文 URL → wechat_file_url=...
Step 6: 发布到微信 → ✅ 发布成功
browser.evaluate(),在浏览器上下文中执行 fetch()