@jackwener/opencli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/ci.yml +26 -0
- package/.github/workflows/release.yml +40 -0
- package/README.md +67 -0
- package/SKILL.md +230 -0
- package/dist/bilibili.d.ts +13 -0
- package/dist/bilibili.js +93 -0
- package/dist/browser.d.ts +48 -0
- package/dist/browser.js +261 -0
- package/dist/clis/bilibili/favorite.d.ts +1 -0
- package/dist/clis/bilibili/favorite.js +39 -0
- package/dist/clis/bilibili/feed.d.ts +1 -0
- package/dist/clis/bilibili/feed.js +64 -0
- package/dist/clis/bilibili/history.d.ts +1 -0
- package/dist/clis/bilibili/history.js +44 -0
- package/dist/clis/bilibili/me.d.ts +1 -0
- package/dist/clis/bilibili/me.js +13 -0
- package/dist/clis/bilibili/search.d.ts +1 -0
- package/dist/clis/bilibili/search.js +24 -0
- package/dist/clis/bilibili/user-videos.d.ts +1 -0
- package/dist/clis/bilibili/user-videos.js +38 -0
- package/dist/clis/github/search.d.ts +1 -0
- package/dist/clis/github/search.js +20 -0
- package/dist/clis/index.d.ts +13 -0
- package/dist/clis/index.js +16 -0
- package/dist/clis/zhihu/search.d.ts +1 -0
- package/dist/clis/zhihu/search.js +58 -0
- package/dist/engine.d.ts +6 -0
- package/dist/engine.js +77 -0
- package/dist/explore.d.ts +17 -0
- package/dist/explore.js +603 -0
- package/dist/generate.d.ts +11 -0
- package/dist/generate.js +134 -0
- package/dist/main.d.ts +5 -0
- package/dist/main.js +117 -0
- package/dist/output.d.ts +11 -0
- package/dist/output.js +98 -0
- package/dist/pipeline.d.ts +9 -0
- package/dist/pipeline.js +315 -0
- package/dist/promote.d.ts +1 -0
- package/dist/promote.js +3 -0
- package/dist/register.d.ts +2 -0
- package/dist/register.js +2 -0
- package/dist/registry.d.ts +50 -0
- package/dist/registry.js +42 -0
- package/dist/runtime.d.ts +12 -0
- package/dist/runtime.js +27 -0
- package/dist/scaffold.d.ts +2 -0
- package/dist/scaffold.js +2 -0
- package/dist/smoke.d.ts +2 -0
- package/dist/smoke.js +2 -0
- package/dist/snapshotFormatter.d.ts +9 -0
- package/dist/snapshotFormatter.js +41 -0
- package/dist/synthesize.d.ts +10 -0
- package/dist/synthesize.js +191 -0
- package/dist/validate.d.ts +2 -0
- package/dist/validate.js +73 -0
- package/dist/verify.d.ts +2 -0
- package/dist/verify.js +9 -0
- package/package.json +47 -0
- package/src/bilibili.ts +111 -0
- package/src/browser.ts +260 -0
- package/src/clis/bilibili/favorite.ts +42 -0
- package/src/clis/bilibili/feed.ts +71 -0
- package/src/clis/bilibili/history.ts +48 -0
- package/src/clis/bilibili/hot.yaml +38 -0
- package/src/clis/bilibili/me.ts +14 -0
- package/src/clis/bilibili/search.ts +25 -0
- package/src/clis/bilibili/user-videos.ts +42 -0
- package/src/clis/github/search.ts +21 -0
- package/src/clis/github/trending.yaml +58 -0
- package/src/clis/hackernews/top.yaml +36 -0
- package/src/clis/index.ts +19 -0
- package/src/clis/twitter/trending.yaml +40 -0
- package/src/clis/v2ex/hot.yaml +29 -0
- package/src/clis/v2ex/latest.yaml +28 -0
- package/src/clis/zhihu/hot.yaml +28 -0
- package/src/clis/zhihu/search.ts +65 -0
- package/src/engine.ts +86 -0
- package/src/explore.ts +648 -0
- package/src/generate.ts +145 -0
- package/src/main.ts +103 -0
- package/src/output.ts +96 -0
- package/src/pipeline.ts +295 -0
- package/src/promote.ts +3 -0
- package/src/register.ts +2 -0
- package/src/registry.ts +87 -0
- package/src/runtime.ts +36 -0
- package/src/scaffold.ts +2 -0
- package/src/smoke.ts +2 -0
- package/src/snapshotFormatter.ts +51 -0
- package/src/synthesize.ts +210 -0
- package/src/validate.ts +55 -0
- package/src/verify.ts +9 -0
- package/tsconfig.json +17 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
check:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v4
|
|
14
|
+
|
|
15
|
+
- uses: actions/setup-node@v4
|
|
16
|
+
with:
|
|
17
|
+
node-version: '22'
|
|
18
|
+
|
|
19
|
+
- name: Install dependencies
|
|
20
|
+
run: npm ci
|
|
21
|
+
|
|
22
|
+
- name: Type check
|
|
23
|
+
run: npx tsc --noEmit
|
|
24
|
+
|
|
25
|
+
- name: Build
|
|
26
|
+
run: npm run build
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- 'v*'
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: write
|
|
10
|
+
id-token: write
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
release:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
|
|
18
|
+
- uses: actions/setup-node@v4
|
|
19
|
+
with:
|
|
20
|
+
node-version: '22'
|
|
21
|
+
registry-url: 'https://registry.npmjs.org'
|
|
22
|
+
|
|
23
|
+
- name: Install dependencies
|
|
24
|
+
run: npm ci
|
|
25
|
+
|
|
26
|
+
- name: Type check
|
|
27
|
+
run: npx tsc --noEmit
|
|
28
|
+
|
|
29
|
+
- name: Build
|
|
30
|
+
run: npm run build
|
|
31
|
+
|
|
32
|
+
- name: Create GitHub Release
|
|
33
|
+
uses: softprops/action-gh-release@v2
|
|
34
|
+
with:
|
|
35
|
+
generate_release_notes: true
|
|
36
|
+
|
|
37
|
+
- name: Publish to npm
|
|
38
|
+
run: npm publish --provenance --access public
|
|
39
|
+
env:
|
|
40
|
+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
package/README.md
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# OpenCLI
|
|
2
|
+
|
|
3
|
+
> **Make any website your CLI.** 操控 Chrome 无风控风险,复用登录,CLI 化全部网站。
|
|
4
|
+
|
|
5
|
+
OpenCLI 是一个 AI Native 的 CLI 工具,通过 Chrome 浏览器 + Playwright MCP Bridge 扩展,将任何网站变成命令行工具。
|
|
6
|
+
|
|
7
|
+
## ✨ 特性
|
|
8
|
+
|
|
9
|
+
- 🌐 **CLI 化全部网站** — 支持 Bilibili、知乎、GitHub、Twitter、V2EX、Hacker News 等
|
|
10
|
+
- 🔐 **零风控风险** — 复用 Chrome 已登录状态,无需存储密码
|
|
11
|
+
- 🤖 **AI Native** — AI agent 可直接探索网站并自动生成新命令
|
|
12
|
+
- 📝 **声明式 YAML** — 用 YAML 定义 pipeline,无需写代码
|
|
13
|
+
- 🔌 **TypeScript 扩展** — 复杂场景用 TS 编写适配器
|
|
14
|
+
|
|
15
|
+
## 🚀 快速开始
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
# 安装依赖
|
|
19
|
+
cd ~/code/ai-native-cli && npm install
|
|
20
|
+
|
|
21
|
+
# 列出所有命令
|
|
22
|
+
npx tsx src/main.ts list
|
|
23
|
+
|
|
24
|
+
# 公共 API(无需浏览器)
|
|
25
|
+
npx tsx src/main.ts hackernews top --limit 10
|
|
26
|
+
npx tsx src/main.ts github search --keyword "typescript"
|
|
27
|
+
|
|
28
|
+
# 浏览器命令(需要 Chrome + Playwright MCP Bridge 扩展)
|
|
29
|
+
npx tsx src/main.ts bilibili hot --limit 10
|
|
30
|
+
npx tsx src/main.ts zhihu hot --limit 10
|
|
31
|
+
npx tsx src/main.ts twitter trending --limit 10
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## 📋 前置要求
|
|
35
|
+
|
|
36
|
+
浏览器命令需要:
|
|
37
|
+
1. Chrome 浏览器正在运行
|
|
38
|
+
2. 安装 [Playwright MCP Bridge](https://chromewebstore.google.com/detail/playwright-mcp-bridge/mmlmfjhmonkocbjadbfplnigmagldckm) 扩展
|
|
39
|
+
3. 首次使用时点击扩展图标批准连接
|
|
40
|
+
|
|
41
|
+
## 📦 内置命令
|
|
42
|
+
|
|
43
|
+
| 站点 | 命令 | 说明 | 模式 |
|
|
44
|
+
|------|------|------|------|
|
|
45
|
+
| bilibili | hot, search, me, favorite, history, feed, user-videos | 热门 / 搜索 / 个人 / 收藏 / 历史 / 动态 / 投稿 | 🔐 浏览器 |
|
|
46
|
+
| zhihu | hot, search | 热榜 / 搜索 | 🔐 浏览器 |
|
|
47
|
+
| github | trending, search | Trending / 搜索 | 🔐 / 🌐 公共 |
|
|
48
|
+
| twitter | trending | 热门话题 | 🔐 浏览器 |
|
|
49
|
+
| v2ex | hot, latest | 热门 / 最新 | 🔐 浏览器 |
|
|
50
|
+
| hackernews | top | 热门故事 | 🌐 公共 API |
|
|
51
|
+
|
|
52
|
+
## 🎨 输出格式
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
opencli bilibili hot -f table # 默认表格
|
|
56
|
+
opencli bilibili hot -f json # JSON(适合管道和 AI agent)
|
|
57
|
+
opencli bilibili hot -f md # Markdown
|
|
58
|
+
opencli bilibili hot -f csv # CSV
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## 🔧 创建新命令
|
|
62
|
+
|
|
63
|
+
参考 [SKILL.md](./SKILL.md) 了解 YAML 和 TypeScript 两种方式创建新的 CLI 适配器。
|
|
64
|
+
|
|
65
|
+
## 📄 License
|
|
66
|
+
|
|
67
|
+
MIT
|
package/SKILL.md
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: opencli
|
|
3
|
+
description: "OpenCLI — Make any website your CLI. Zero setup, AI-powered. Turn any website into CLI commands via Chrome browser."
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
author: jackwener
|
|
6
|
+
tags: [cli, browser, web, mcp, playwright, bilibili, zhihu, twitter, github, v2ex, hackernews, 哔哩哔哩, 知乎, AI, agent]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# OpenCLI
|
|
10
|
+
|
|
11
|
+
> Make any website your CLI. 操控 Chrome 无风控风险,复用登录,CLI 化全部网站。
|
|
12
|
+
|
|
13
|
+
## 安装
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
cd ~/code/ai-native-cli
|
|
17
|
+
npm install
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## 使用方式
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
# 通过 npx 运行(推荐)
|
|
24
|
+
npx tsx src/main.ts <command>
|
|
25
|
+
|
|
26
|
+
# 或者构建后运行
|
|
27
|
+
npm run build && node dist/main.js <command>
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## 前置要求
|
|
31
|
+
|
|
32
|
+
浏览器命令需要:
|
|
33
|
+
1. Chrome 浏览器正在运行
|
|
34
|
+
2. 安装 [Playwright MCP Bridge](https://chromewebstore.google.com/detail/playwright-mcp-bridge/mmlmfjhmonkocbjadbfplnigmagldckm) 扩展
|
|
35
|
+
3. 点击扩展图标批准连接
|
|
36
|
+
|
|
37
|
+
公共 API 命令(hackernews、github search)无需浏览器。
|
|
38
|
+
|
|
39
|
+
## 内置命令
|
|
40
|
+
|
|
41
|
+
### 数据查询
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
# Bilibili
|
|
45
|
+
opencli bilibili hot --limit 10 # B站热门视频
|
|
46
|
+
opencli bilibili search --keyword "rust" # 搜索视频
|
|
47
|
+
opencli bilibili me # 我的信息
|
|
48
|
+
opencli bilibili favorite # 我的收藏
|
|
49
|
+
opencli bilibili history --limit 20 # 观看历史
|
|
50
|
+
opencli bilibili feed --limit 10 # 动态时间线
|
|
51
|
+
opencli bilibili user-videos --uid 12345 # 用户投稿
|
|
52
|
+
|
|
53
|
+
# 知乎
|
|
54
|
+
opencli zhihu hot --limit 10 # 知乎热榜
|
|
55
|
+
opencli zhihu search --keyword "AI" # 搜索
|
|
56
|
+
|
|
57
|
+
# GitHub
|
|
58
|
+
opencli github trending --limit 10 # GitHub Trending
|
|
59
|
+
opencli github search --keyword "cli" # 搜索仓库(无需浏览器)
|
|
60
|
+
|
|
61
|
+
# Twitter/X
|
|
62
|
+
opencli twitter trending --limit 10 # 热门话题
|
|
63
|
+
|
|
64
|
+
# V2EX
|
|
65
|
+
opencli v2ex hot --limit 10 # 热门话题
|
|
66
|
+
opencli v2ex latest --limit 10 # 最新话题
|
|
67
|
+
|
|
68
|
+
# Hacker News
|
|
69
|
+
opencli hackernews top --limit 10 # 热门故事(无需浏览器)
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### 管理命令
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
opencli list # 列出所有可用命令
|
|
76
|
+
opencli list --json # JSON 格式输出
|
|
77
|
+
opencli validate # 验证所有 CLI 定义
|
|
78
|
+
opencli validate bilibili # 验证指定站点
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### AI 工作流(为 AI Agent 设计)
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
opencli explore <url> # 探索网站,生成 API 发现成果物
|
|
85
|
+
opencli synthesize <site> # 从探索成果物合成候选 CLI
|
|
86
|
+
opencli generate <url> --goal "hot" # 一键:探索 → 合成 → 注册
|
|
87
|
+
opencli verify <site/name> --smoke # 验证 + Smoke 测试
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## 输出格式
|
|
91
|
+
|
|
92
|
+
所有命令支持 `--format` / `-f` 选项:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
opencli bilibili hot -f table # 默认表格
|
|
96
|
+
opencli bilibili hot -f json # JSON
|
|
97
|
+
opencli bilibili hot -f md # Markdown
|
|
98
|
+
opencli bilibili hot -f csv # CSV
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## 调试
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
opencli bilibili hot -v # 显示 pipeline 每步详情
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## 创建新的 CLI 适配器
|
|
108
|
+
|
|
109
|
+
### YAML 方式(声明式,推荐)
|
|
110
|
+
|
|
111
|
+
在 `src/clis/<site>/<name>.yaml` 创建文件:
|
|
112
|
+
|
|
113
|
+
```yaml
|
|
114
|
+
site: mysite
|
|
115
|
+
name: hot
|
|
116
|
+
description: Hot topics on mysite
|
|
117
|
+
domain: www.mysite.com
|
|
118
|
+
strategy: cookie # public | cookie | header | intercept | ui
|
|
119
|
+
browser: true
|
|
120
|
+
|
|
121
|
+
args:
|
|
122
|
+
limit:
|
|
123
|
+
type: int
|
|
124
|
+
default: 20
|
|
125
|
+
description: Number of items
|
|
126
|
+
|
|
127
|
+
pipeline:
|
|
128
|
+
- navigate: https://www.mysite.com
|
|
129
|
+
|
|
130
|
+
- evaluate: |
|
|
131
|
+
(async () => {
|
|
132
|
+
const res = await fetch('/api/hot', { credentials: 'include' });
|
|
133
|
+
return await res.json();
|
|
134
|
+
})()
|
|
135
|
+
|
|
136
|
+
- select: data.items
|
|
137
|
+
|
|
138
|
+
- map:
|
|
139
|
+
rank: ${{ index + 1 }}
|
|
140
|
+
title: ${{ item.title }}
|
|
141
|
+
score: ${{ item.score }}
|
|
142
|
+
|
|
143
|
+
- limit: ${{ args.limit }}
|
|
144
|
+
|
|
145
|
+
columns: [rank, title, score]
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### TypeScript 方式(编程式,更灵活)
|
|
149
|
+
|
|
150
|
+
在 `src/clis/<site>/<name>.ts` 创建并在 `clis/index.ts` 中 import:
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
import { cli, Strategy } from '../../registry.js';
|
|
154
|
+
|
|
155
|
+
cli({
|
|
156
|
+
site: 'mysite',
|
|
157
|
+
name: 'search',
|
|
158
|
+
strategy: Strategy.COOKIE,
|
|
159
|
+
args: [{ name: 'keyword', required: true }],
|
|
160
|
+
columns: ['rank', 'title', 'url'],
|
|
161
|
+
func: async (page, kwargs) => {
|
|
162
|
+
const data = await page.evaluate(`
|
|
163
|
+
async () => {
|
|
164
|
+
const res = await fetch('/api/search?q=${kwargs.keyword}', { credentials: 'include' });
|
|
165
|
+
return await res.json();
|
|
166
|
+
}
|
|
167
|
+
`);
|
|
168
|
+
return data.items.map((item, i) => ({
|
|
169
|
+
rank: i + 1,
|
|
170
|
+
title: item.title,
|
|
171
|
+
url: item.url,
|
|
172
|
+
}));
|
|
173
|
+
},
|
|
174
|
+
});
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Pipeline 步骤参考
|
|
178
|
+
|
|
179
|
+
| 步骤 | 说明 | 示例 |
|
|
180
|
+
|------|------|------|
|
|
181
|
+
| `navigate` | 导航到 URL | `navigate: https://example.com` |
|
|
182
|
+
| `fetch` | HTTP 请求(使用浏览器 cookie) | `fetch: { url: "...", params: { q: "${{ args.keyword }}" } }` |
|
|
183
|
+
| `evaluate` | 执行 JavaScript | `evaluate: \| (async () => { ... })()` |
|
|
184
|
+
| `select` | 选取 JSON 路径 | `select: data.items` |
|
|
185
|
+
| `map` | 映射字段 | `map: { title: "${{ item.title }}" }` |
|
|
186
|
+
| `filter` | 过滤 | `filter: item.score > 100` |
|
|
187
|
+
| `sort` | 排序 | `sort: { by: score, order: desc }` |
|
|
188
|
+
| `limit` | 限制数量 | `limit: ${{ args.limit }}` |
|
|
189
|
+
| `snapshot` | 获取页面快照 | `snapshot: { interactive: true }` |
|
|
190
|
+
| `click` | 点击元素 | `click: ${{ ref }}` |
|
|
191
|
+
| `type` | 输入文本 | `type: { ref: "@1", text: "hello" }` |
|
|
192
|
+
| `wait` | 等待 | `wait: 2` 或 `wait: { text: "loaded" }` |
|
|
193
|
+
| `press` | 按键 | `press: Enter` |
|
|
194
|
+
|
|
195
|
+
## 模板语法
|
|
196
|
+
|
|
197
|
+
使用 `${{ expression }}` 进行模板替换:
|
|
198
|
+
|
|
199
|
+
```yaml
|
|
200
|
+
# 引用参数
|
|
201
|
+
${{ args.keyword }}
|
|
202
|
+
${{ args.limit | default(20) }}
|
|
203
|
+
|
|
204
|
+
# 引用当前 item(在 map/filter 中)
|
|
205
|
+
${{ item.title }}
|
|
206
|
+
${{ item.data.nested.field }}
|
|
207
|
+
|
|
208
|
+
# 索引(从 0 开始)
|
|
209
|
+
${{ index }}
|
|
210
|
+
${{ index + 1 }}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
## 环境变量
|
|
214
|
+
|
|
215
|
+
| 变量 | 默认值 | 说明 |
|
|
216
|
+
|------|--------|------|
|
|
217
|
+
| `OPENCLI_BROWSER_CONNECT_TIMEOUT` | 30 | 浏览器连接超时(秒) |
|
|
218
|
+
| `OPENCLI_BROWSER_COMMAND_TIMEOUT` | 45 | 命令执行超时(秒) |
|
|
219
|
+
| `OPENCLI_BROWSER_EXPLORE_TIMEOUT` | 120 | Explore 超时(秒) |
|
|
220
|
+
| `OPENCLI_EXTENSION_LOCK_TIMEOUT` | 120 | 扩展锁超时(秒) |
|
|
221
|
+
| `PLAYWRIGHT_MCP_EXTENSION_TOKEN` | — | 自动批准扩展连接 |
|
|
222
|
+
|
|
223
|
+
## 错误排查
|
|
224
|
+
|
|
225
|
+
| 错误 | 解决方案 |
|
|
226
|
+
|------|----------|
|
|
227
|
+
| `npx not found` | 安装 Node.js: `brew install node` |
|
|
228
|
+
| `Timed out connecting to browser` | 1) 确认 Chrome 已打开 2) 安装 Playwright MCP Bridge 扩展 3) 点击扩展图标批准 |
|
|
229
|
+
| `Extension lock timed out` | 等待其他 opencli 命令完成,浏览器命令需串行运行 |
|
|
230
|
+
| `Request timed out` | 增大 `OPENCLI_BROWSER_COMMAND_TIMEOUT` 或检查网络 |
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bilibili shared helpers: WBI signing, authenticated fetch, nav data, UID resolution.
|
|
3
|
+
*/
|
|
4
|
+
export declare function stripHtml(s: string): string;
|
|
5
|
+
export declare function payloadData(payload: any): any;
|
|
6
|
+
export declare function wbiSign(page: any, params: Record<string, any>): Promise<Record<string, string>>;
|
|
7
|
+
export declare function apiGet(page: any, path: string, opts?: {
|
|
8
|
+
params?: Record<string, any>;
|
|
9
|
+
signed?: boolean;
|
|
10
|
+
}): Promise<any>;
|
|
11
|
+
export declare function fetchJson(page: any, url: string): Promise<any>;
|
|
12
|
+
export declare function getSelfUid(page: any): Promise<string>;
|
|
13
|
+
export declare function resolveUid(page: any, input: string): Promise<string>;
|
package/dist/bilibili.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bilibili shared helpers: WBI signing, authenticated fetch, nav data, UID resolution.
|
|
3
|
+
*/
|
|
4
|
+
const MIXIN_KEY_ENC_TAB = [
|
|
5
|
+
46, 47, 18, 2, 53, 8, 23, 32, 15, 50, 10, 31, 58, 3, 45, 35, 27, 43, 5, 49,
|
|
6
|
+
33, 9, 42, 19, 29, 28, 14, 39, 12, 38, 41, 13, 37, 48, 7, 16, 24, 55, 40,
|
|
7
|
+
61, 26, 17, 0, 1, 60, 51, 30, 4, 22, 25, 54, 21, 56, 59, 6, 63, 57, 62, 11,
|
|
8
|
+
36, 20, 34, 44, 52,
|
|
9
|
+
];
|
|
10
|
+
export function stripHtml(s) {
|
|
11
|
+
return s.replace(/<[^>]+>/g, '').replace(/&[a-z]+;/gi, ' ').trim();
|
|
12
|
+
}
|
|
13
|
+
export function payloadData(payload) {
|
|
14
|
+
return payload?.data ?? payload;
|
|
15
|
+
}
|
|
16
|
+
async function getNavData(page) {
|
|
17
|
+
return page.evaluate(`
|
|
18
|
+
async () => {
|
|
19
|
+
const res = await fetch('https://api.bilibili.com/x/web-interface/nav', { credentials: 'include' });
|
|
20
|
+
return await res.json();
|
|
21
|
+
}
|
|
22
|
+
`);
|
|
23
|
+
}
|
|
24
|
+
async function getWbiKeys(page) {
|
|
25
|
+
const nav = await getNavData(page);
|
|
26
|
+
const wbiImg = nav?.data?.wbi_img ?? {};
|
|
27
|
+
const imgUrl = wbiImg.img_url ?? '';
|
|
28
|
+
const subUrl = wbiImg.sub_url ?? '';
|
|
29
|
+
const imgKey = imgUrl.split('/').pop()?.split('.')[0] ?? '';
|
|
30
|
+
const subKey = subUrl.split('/').pop()?.split('.')[0] ?? '';
|
|
31
|
+
return { imgKey, subKey };
|
|
32
|
+
}
|
|
33
|
+
function getMixinKey(imgKey, subKey) {
|
|
34
|
+
const raw = imgKey + subKey;
|
|
35
|
+
return MIXIN_KEY_ENC_TAB.map(i => raw[i] || '').join('').slice(0, 32);
|
|
36
|
+
}
|
|
37
|
+
async function md5(text) {
|
|
38
|
+
const { createHash } = await import('node:crypto');
|
|
39
|
+
return createHash('md5').update(text).digest('hex');
|
|
40
|
+
}
|
|
41
|
+
export async function wbiSign(page, params) {
|
|
42
|
+
const { imgKey, subKey } = await getWbiKeys(page);
|
|
43
|
+
const mixinKey = getMixinKey(imgKey, subKey);
|
|
44
|
+
const wts = Math.floor(Date.now() / 1000);
|
|
45
|
+
const sorted = {};
|
|
46
|
+
const allParams = { ...params, wts: String(wts) };
|
|
47
|
+
for (const key of Object.keys(allParams).sort()) {
|
|
48
|
+
sorted[key] = String(allParams[key]).replace(/[!'()*]/g, '');
|
|
49
|
+
}
|
|
50
|
+
const query = new URLSearchParams(sorted).toString();
|
|
51
|
+
const wRid = await md5(query + mixinKey);
|
|
52
|
+
sorted.w_rid = wRid;
|
|
53
|
+
return sorted;
|
|
54
|
+
}
|
|
55
|
+
export async function apiGet(page, path, opts = {}) {
|
|
56
|
+
const baseUrl = 'https://api.bilibili.com';
|
|
57
|
+
let params = opts.params ?? {};
|
|
58
|
+
if (opts.signed) {
|
|
59
|
+
params = await wbiSign(page, params);
|
|
60
|
+
}
|
|
61
|
+
const qs = new URLSearchParams(Object.fromEntries(Object.entries(params).map(([k, v]) => [k, String(v)])));
|
|
62
|
+
const url = `${baseUrl}${path}?${qs}`;
|
|
63
|
+
return fetchJson(page, url);
|
|
64
|
+
}
|
|
65
|
+
export async function fetchJson(page, url) {
|
|
66
|
+
const escapedUrl = url.replace(/"/g, '\\"');
|
|
67
|
+
return page.evaluate(`
|
|
68
|
+
async () => {
|
|
69
|
+
const res = await fetch("${escapedUrl}", { credentials: "include" });
|
|
70
|
+
return await res.json();
|
|
71
|
+
}
|
|
72
|
+
`);
|
|
73
|
+
}
|
|
74
|
+
export async function getSelfUid(page) {
|
|
75
|
+
const nav = await getNavData(page);
|
|
76
|
+
const mid = nav?.data?.mid;
|
|
77
|
+
if (!mid)
|
|
78
|
+
throw new Error('Not logged in to Bilibili');
|
|
79
|
+
return String(mid);
|
|
80
|
+
}
|
|
81
|
+
export async function resolveUid(page, input) {
|
|
82
|
+
if (/^\d+$/.test(input))
|
|
83
|
+
return input;
|
|
84
|
+
// Search for user by name
|
|
85
|
+
const payload = await apiGet(page, '/x/web-interface/wbi/search/type', {
|
|
86
|
+
params: { search_type: 'bili_user', keyword: input },
|
|
87
|
+
signed: true,
|
|
88
|
+
});
|
|
89
|
+
const results = payload?.data?.result ?? [];
|
|
90
|
+
if (results.length > 0)
|
|
91
|
+
return String(results[0].mid);
|
|
92
|
+
throw new Error(`Cannot resolve UID for: ${input}`);
|
|
93
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser interaction via Playwright MCP Bridge extension.
|
|
3
|
+
* Connects to an existing Chrome browser through the extension's stdio JSON-RPC.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Page abstraction wrapping JSON-RPC calls to Playwright MCP.
|
|
7
|
+
*/
|
|
8
|
+
export declare class Page {
|
|
9
|
+
private _send;
|
|
10
|
+
private _recv;
|
|
11
|
+
constructor(_send: (msg: string) => void, _recv: () => Promise<any>);
|
|
12
|
+
call(method: string, params?: Record<string, any>): Promise<any>;
|
|
13
|
+
goto(url: string): Promise<void>;
|
|
14
|
+
evaluate(js: string): Promise<any>;
|
|
15
|
+
snapshot(opts?: {
|
|
16
|
+
interactive?: boolean;
|
|
17
|
+
compact?: boolean;
|
|
18
|
+
maxDepth?: number;
|
|
19
|
+
raw?: boolean;
|
|
20
|
+
}): Promise<any>;
|
|
21
|
+
click(ref: string): Promise<void>;
|
|
22
|
+
typeText(ref: string, text: string): Promise<void>;
|
|
23
|
+
pressKey(key: string): Promise<void>;
|
|
24
|
+
wait(seconds: number): Promise<void>;
|
|
25
|
+
tabs(): Promise<any>;
|
|
26
|
+
closeTab(index?: number): Promise<void>;
|
|
27
|
+
newTab(): Promise<void>;
|
|
28
|
+
selectTab(index: number): Promise<void>;
|
|
29
|
+
networkRequests(includeStatic?: boolean): Promise<any>;
|
|
30
|
+
consoleMessages(level?: string): Promise<any>;
|
|
31
|
+
scroll(direction?: string, amount?: number): Promise<void>;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Playwright MCP process manager.
|
|
35
|
+
*/
|
|
36
|
+
export declare class PlaywrightMCP {
|
|
37
|
+
private _proc;
|
|
38
|
+
private _buffer;
|
|
39
|
+
private _waiters;
|
|
40
|
+
private _lockAcquired;
|
|
41
|
+
private _initialTabCount;
|
|
42
|
+
connect(opts?: {
|
|
43
|
+
timeout?: number;
|
|
44
|
+
}): Promise<Page>;
|
|
45
|
+
close(): Promise<void>;
|
|
46
|
+
private _acquireLock;
|
|
47
|
+
private _releaseLock;
|
|
48
|
+
}
|