将 企业微信第三方应用市场 CLI 化的 OpenCLI 插件,覆盖分类/搜索/详情/清单同步/开通/卸载全流程原子能力。
- 站点 slug:
wecom-store - 命令数: 16
- 依赖: @jackwener/opencli
>=1.8.0 - 数据目录:
~/.opencli/data/wecom-store/
# 假定已全局安装 opencli
npm i -g @jackwener/opencli
# 本仓库以 symlink 形式挂到 ~/.opencli/plugins/
opencli plugin install /path/to/opencli-plugin-wecom-store安装后 opencli list 应能看到 wecom-store 分组下 16 条命令。
| 命令 | 策略 | 需登录 | 用途 |
|---|---|---|---|
search |
PUBLIC | 建议 | 关键词搜索(匿名调用返回空,走本地索引更靠谱) |
category-list |
PUBLIC | 否 | 拉取分类树(12 一级 + 26 二级 = 28 组合) |
category-apps |
PUBLIC | 否 | 单个 (id, subId) 下的全量应用 |
home-recommend |
PUBLIC | 否 | 首页推荐位应用 |
industry-list |
PUBLIC | 否 | 行业解决方案 |
hardware-list |
PUBLIC | 否 | 硬件生态清单 |
app-price |
PUBLIC | 否 | 应用套餐/价格信息(可匿名读取的部分) |
app-get |
COOKIE | 是 | 应用详情(getAppDetail,匿名会 -30001) |
app-history |
COOKIE | 是 | 应用版本历史(同上) |
me |
COOKIE | 是 | 登录态验证:拿 corp_id / user_id / role |
index-sync |
PUBLIC | 否 | 全量/增量同步应用清单到本地 |
index-query |
LOCAL | 否 | 从本地清单按条件筛(离线,快) |
index-diff |
LOCAL | 否 | 展示最近的增量 diff 事件 |
capture-install |
COOKIE | 是 | 抓取「开通应用」完整请求链 |
install |
COOKIE | 是 | 开通免费/试用版应用,默认 scope=self |
uninstall |
COOKIE | 是 | 卸载/停用已开通应用 |
# 分类树
opencli wecom-store category-list
# 某个二级分类的全量应用
opencli wecom-store category-apps --id 1 --subId 1002
# 全量同步 (~5-8s,抓 28 个分类页 SSR)
opencli wecom-store index-sync --mode full
# 本地查询
opencli wecom-store index-query --q AI --limit 10
opencli wecom-store index-query --category "客户联系"
opencli wecom-store index-query --brand "腾讯"index-sync 会写以下文件到 ~/.opencli/data/wecom-store/:
apps.json—{updated_at, apps[{thirdapp_id, name, slogan, logo, brand_name, category_id, ...}]}fingerprints.json— sha1 指纹,用于增量 diffhistory.jsonl— 每次 sync 的 added/removed/changed 事件流
增量:
opencli wecom-store index-sync --mode incremental
opencli wecom-store index-diff --limit 20登录:直接在系统 Chrome 打开 https://open.work.weixin.qq.com/appStore/ 用管理员账号登过,OpenCLI 通过扩展复用登录态。
# 验证登录态
opencli wecom-store me
# 应用详情
opencli wecom-store app-get "MTk3MDMyNDk4ODA3OTE0MV8xMDMyMDE4XzE="
# 版本历史
opencli wecom-store app-history "MTk3MDMyNDk4ODA3OTE0MV8xMDMyMDE4XzE="thirdapp_id 就是应用详情页 URL 中 /appStore/app/ 后面的 base64 段,也可以从 index-query / category-apps 输出中拷贝。
--dry-run(默认开启):只打印计划,不发请求。--confirm:真正执行前必须显式加。- 每次执行都会记 audit log 到
~/.opencli/data/wecom-store/install-audit.jsonl。
真实的 install 是 两 origin、两步链(2026-07-01 端到端实测跑通):
step0 (前置查询) POST open.work.weixin.qq.com/wwopen/monoApi/appStore/app/getAppDetail
body : { appId, app_version:'', termid:65539 }
产出 : app_name / on_trial_type
(numeric suiteid + ww_corpid 从 app_info.update_content_list[0] 提取,
或者 fallback 到本地 index-sync 缓存)
step1 (拿授权码) POST open.work.weixin.qq.com/wwopen/monoApi/appStore/auth/getPreAuthCode
body : { service_corpid: <数字>, suiteid: <数字> }
产出 : { data: { kitid, preauthcode } }
step2 (提交安装) POST work.weixin.qq.com/wework_admin/third/installApp
(⚠️ 不同 origin! form-urlencoded, 不是 JSON)
body : redirect_uri=&pre_auth_code=<code>&suite_id=<数字>
&install_app_item[0][thirdapp_id]=<b64>
&install_app_item[0][vid_item][]=<self_vid>
&install_app_item[0][custom_acquisition_permit]=
&b_can_auth_customer_acquisition=false
&opt[]=7&from_ad_card=&scene=&scene_val=
产出 : { data: { install_app_id: [<numeric>] } }
体验流程:
# 一次性:全量 index-sync 拿全站 numeric suiteid + ww_corpid 缓存
opencli wecom-store index-sync --mode full
# 干跑(默认)— 输出计划: service_corpid / suiteid / vid_item / opt
opencli wecom-store install <thirdapp_id> --self-vid <vid> --plan trial
# 真跑
opencli wecom-store install <thirdapp_id> --self-vid <vid> --plan trial \
--dry-run=false --confirm=true参数:
thirdapp_ids(位置参数,必填)— 逗号分隔的应用 id 列表 (base64)--plan free|trial(默认trial)— 拒绝paid--scope self|vid:<vid,vid>(默认self)— 可见范围(vid,不是 user_id)--self-vid <vid>— 当前管理员 vid,用于install_app_item[0][vid_item][];缺省会尝试从window.currentUser/wework_admin/main/getAdminList探测,找不到就报错--opt <数字或列表>(默认7)—opt[]值,试用/免费默认为7--dry-run/--dry-run=false— 默认true--confirm— 真跑必须加,与--dry-run=false双重确认
如何拿 self vid:打开 https://work.weixin.qq.com/wework_admin/frame#apps → DevTools → Network → 任一 XHR 的响应/请求 body 中含 vid 字段(如 1688857901263362)。此 vid 与 open.work 上 getMarketUserInfo 返回的 corpid 不是同一个。
卸载:
opencli wecom-store uninstall "APPID_A" --confirm ┌─ SSR HTML (Nuxt2) → 分类/首页/行业/硬件
open.work.weixin.qq.com/appStore ├─ monoApi/appStore/* (POST JSON) → 4 个免登 + 登录端点
└─ 详情页 SSR (需登录)
work.weixin.qq.com/wework_admin ── /third/installApp (POST form-urlencoded, 需登录)
↑ install/uninstall 的真正提交口
- SSR:
fetchSSR()(Node 端),解析window.__NUXT__=...— 支持新的 JSON+IIFE 混合形态 - 免登 monoApi:
postMonoApi()(Node 端) — 4 个:marketSearch / getAppDetail / getAppHistory / getMarketUserInfo - 登录 monoApi (JSON):
postMonoApiInPage()(browser 端 fetch,credentials:'include') - 登录 wework_admin (form-urlencoded):
postFormInPage()(browser 端 fetch, 目标 originwork.weixin.qq.com, 靠credentials:'include'自动附另一 origin 的 sso Cookie)
关键坑(血泪总结):
- 所有 monoApi 端点必须带 QS
?lang=zh_CN&ajax=1&f=json&random=<n>,否则一律errCode=-30001 参数错误。已在withAjaxQs()里自动附上。 getAppDetailbody 是 camelCaseappId+app_version+termid:65539,不是appid。- 详情页 URL 是
?appId=<b64>&source=homeCategory查询串,不是/app/<b64>路径。 service_corpid与suiteid都是数字;getAppDetail顶层app_info反而没有它们,只在app_info.update_content_list[]里泄露。install 命令会 fallback 到~/.opencli/data/wecom-store/apps.json。servicecorp_info.servicecorp_id是字符串kitid(如ww1ebe7793c4c8bfb0),不是数字 corpid。- step2 的 origin 是
work.weixin.qq.com(不是 open.work),标签页可以停在 open.work,fetch(..., credentials:'include')会自动补另一 origin 的 sso Cookie。
每个 app 的指纹 = sha1(thirdapp_id | name | slogan | brand_name | logo | category_id | sub_category_id)。字段变了才写 changed 事件。
opencli-plugin-wecom-store/
├── opencli-plugin.json # 插件清单
├── package.json
├── README.md
├── shared/
│ ├── endpoints.js # 端点常量 & Referer/Header
│ ├── nuxt.js # __NUXT__ 提取器 (JSON+IIFE 混合)
│ ├── api.js # fetchSSR / postMonoApi / postMonoApiInPage
│ ├── home.js # 首页 Nuxt 缓存 & 分类枚举
│ └── index-store.js # ~/.opencli/data/wecom-store 读写
├── search.js # PUBLIC - 关键词搜索
├── category-list.js # PUBLIC - 分类树
├── category-apps.js # PUBLIC - 分类下应用
├── home-recommend.js # PUBLIC - 首页推荐
├── industry-list.js # PUBLIC - 行业方案
├── hardware-list.js # PUBLIC - 硬件清单
├── app-price.js # PUBLIC - 价格
├── app-get.js # COOKIE - 应用详情
├── app-history.js # COOKIE - 版本历史
├── me.js # COOKIE - 登录态
├── index-sync.js # PUBLIC - 全量/增量同步
├── index-query.js # LOCAL - 本地筛选
├── index-diff.js # LOCAL - 增量事件
├── capture-install.js # COOKIE - 抓开通请求链
├── install.js # COOKIE - 开通
└── uninstall.js # COOKIE - 卸载
search匿名返回空item[],需登录才有真结果。离线场景请用index-sync + index-query。install已在真机端到端跑通(2026-07-01, 励销CRM 试用版, install_app_id=5629502485775491);uninstall尚未抓包实测,建议先用capture-install对齐。on_trial_type/app_flag等布尔含义未文档化,来自实测的ctrlState.home字段。- 部分二级分类共用同一 id,
total字段是分区块聚合而非游标,不能作为分页依据。
沿用 OpenCLI 标准:
2— ArgumentError(参数非法)66— EmptyResultError(无匹配)77— AuthRequiredError(登录/Cookie 失效)75— TimeoutError(20s 内无响应)1— CommandExecutionError(HTTP 异常/解析失败)
MIT