MusicBrainz
Browser Setup
All browser work uses Playwright via a Node.js script. Install once per session:
CODEBLOCK0
Launch pattern (use in every script):
CODEBLOCK1
Architecture: Isolated Steps
There are four operations. Each is a self-contained Playwright script. Never combine them into one script. Run them sequentially:
- 1. Login — authenticate the browser session
- Add Artist — create a new artist entity
- Add Release — create a release with tracks, metadata, and Spotify link
- Upload Cover Art — attach album artwork to an existing release
Between steps, close the browser (await browser.close()). Each script launches fresh, logs in if needed, does its one job, and exits. This prevents state pollution between operations.
Preflight — Credentials Check
Run before any write operation. Skip for read-only lookups.
CODEBLOCK2
Credentials live in .credentials.json in the skill directory.
- -
MISSING_CREDENTIALS: Ask user for MusicBrainz username + password. Save:
cat > ~/.openclaw/skills/musicbrainz/.credentials.json << 'EOF'
{"username": "USER", "password": "PASS"}
EOF
- -
INVALID_CREDENTIALS: Ask user to verify, update file, re-run. - No account → https://musicbrainz.org/register
Step 1: Login
Every Playwright script that writes to MB must start with login. Use this exact pattern:
CODEBLOCK4
Key gotcha
The MusicBrainz login page has
two forms: a search form and the login form. Both have
button[type="submit"]. Using
page.click('button[type="submit"]') hits the search button.
Always use the evaluate pattern above to find the button by text content.
Step 2: Add Artist
When: Artist doesn't exist on MusicBrainz. Check first with mb_lookup.sh or the search API.
Script structure
CODEBLOCK5
After completion
Report the artist MBID and link to user. Store the MBID — needed for adding releases.
Step 3: Add Release
Every release follows this identical flow, regardless of what happened before (login, artist creation, previous releases). Each is a fresh Playwright script.
Form field IDs (for reference)
| Field | ID | Values |
|---|
| Title | INLINECODE7 | text |
| Artist |
ac-source-single-artist | text (autocomplete) |
| Release group |
release-group | text (autocomplete) |
| Primary type |
primary-type |
1=Album,
2=Single,
3=EP |
| Status |
status |
1=Official |
| Language |
language |
120=English,
145=German |
| Script |
script |
28=Latin,
12=Cyrillic |
| Year |
event-date-0 | YYYY |
| Month |
.partial-date-month | MM |
| Day |
.partial-date-day | DD |
| Country |
country-0 |
240=[Worldwide] |
| Label |
label-0 | text (autocomplete) |
| Barcode |
barcode | text |
| No barcode |
no-barcode | checkbox |
| Packaging |
packaging |
7=None |
Known traps
- 1. Release group autocomplete blocks the form. After filling the title and selecting an artist, the release group field auto-searches and opens a popup dialog ("Add medium") that makes all other form fields invisible to Playwright (offsetParent = null, zero-size rects). You must dismiss this before interacting with other fields.
- 2. Artist autocomplete can match wrong entity. Typing "The Bride Wore Black" and pressing ArrowDown+Enter may select a similarly-named release group or a different artist. Use the MBID in the autocomplete field for exact matching.
- 3. React selects don't respond to
page.selectOption() when not visible. Use evaluate with native setters instead.
- 4. Track parser dialog vs Add Medium dialog. The track parser opens in a jQuery UI dialog. There may be multiple dialogs open. Always target by dialog title text.
- 5. Track parser may double tracks. The parse can produce duplicate entries. Always verify track count after parsing.
Script structure
CODEBLOCK6
Step 4: Upload Cover Art
Run as a separate script after the release exists.
CODEBLOCK7
Cover art URL from Spotify
Find the image hash on the album page (prefix
ab67616d00001e02 = 300px). Replace with
ab67616d0000b273 for 640×640:
CODEBLOCK8
Download before upload
Always download the image locally first with
curl, then use
setInputFiles(). Do NOT use in-page
fetch() — it can hit CORS issues and the jQuery uploader's signature flow doesn't reliably trigger.
Lookups (API — no login needed)
CODEBLOCK9
Returns MB entity ID or "NOT FOUND". For other queries see references/api.md.
Search by name (no Spotify link)
CODEBLOCK10
Scraping Spotify
Use the oembed endpoint to get the artist/album name without a browser:
CODEBLOCK11
For full discography scraping, use Playwright on /artist/<id>/discography/all and expand the viewport incrementally until all lazy-loaded content renders (see the expand loop pattern in references/api.md).
Progress Reporting (MANDATORY)
Message the user after EACH discrete operation completes:
- - Artist created → name + MBID + link
- Release submitted → title + MBID + link
- Cover art uploaded → confirmation
Never go silent for more than a few minutes during multi-release work.
Key Facts
- - New accounts are "Beginner" — edits queue for 7-day vote
- Digital releases: Packaging=None, Country=[Worldwide], no barcode
- MusicBrainz uses Knockout.js (not React) for forms — native property setters +
dispatchEvent are required - INLINECODE43 only works when the select element is visible in the viewport; use
evaluate when elements may be hidden behind dialogs - Seeding docs: https://musicbrainz.org/doc/Development/Seeding/Release_Editor
MusicBrainz
浏览器设置
所有浏览器操作均通过 Node.js 脚本使用 Playwright。每次会话安装一次:
bash
cd /tmp && npm ls playwright 2>/dev/null || npm install playwright
启动模式(在每个脚本中使用):
javascript
import { chromium } from playwright;
import { execSync } from child_process;
// 动态检测 Playwright 捆绑的 Chromium 路径
const chromiumPath = process.env.PLAYWRIGHTCHROMIUMPATH
|| execSync(find ~/.cache/ms-playwright -name chrome -path /chrome-linux/chrome 2>/dev/null | head -1, { encoding: utf-8 }).trim()
|| execSync(find ~/.cache/ms-playwright -name Chromium -path */Chromium.app/Contents/MacOS/Chromium 2>/dev/null | head -1, { encoding: utf-8 }).trim();
if (!chromiumPath) throw new Error(未找到 Chromium。运行:npx playwright install chromium);
const browser = await chromium.launch({
executablePath: chromiumPath,
headless: true,
args: [--no-sandbox]
});
const page = await browser.newPage();
架构:独立步骤
共有 四个操作。每个操作都是一个独立的 Playwright 脚本。切勿将它们合并到一个脚本中。按顺序运行:
- 1. 登录 — 验证浏览器会话
- 添加艺术家 — 创建新的艺术家实体
- 添加发行版 — 创建包含曲目、元数据和 Spotify 链接的发行版
- 上传封面 — 为现有发行版添加专辑封面
步骤之间,关闭浏览器(await browser.close())。每个脚本重新启动,如果需要则登录,完成其单一任务后退出。这可以防止操作之间的状态污染。
预检 — 凭据检查
在任何写入操作之前运行。 只读查询可跳过。
bash
bash scripts/preflight.sh
凭据存放在技能目录下的 .credentials.json 文件中。
- - MISSING_CREDENTIALS:要求用户提供 MusicBrainz 用户名和密码。保存:
bash
cat > ~/.openclaw/skills/musicbrainz/.credentials.json << EOF
{username: USER, password: PASS}
EOF
- - INVALID_CREDENTIALS:要求用户验证、更新文件并重新运行。
- 无账户 → https://musicbrainz.org/register
步骤 1:登录
每个向 MB 写入的 Playwright 脚本都必须以登录开始。使用此精确模式:
javascript
import { readFileSync } from fs;
const creds = JSON.parse(readFileSync(
process.env.HOME + /.openclaw/skills/musicbrainz/.credentials.json
));
await page.goto(https://musicbrainz.org/login, { waitUntil: networkidle, timeout: 30000 });
await page.fill(#id-username, creds.username);
await page.fill(#id-password, creds.password);
// 重要:页面上有两个提交按钮(搜索 + 登录)。
// 必须点击包含Log in文本的那个。
await page.evaluate(() => {
Array.from(document.querySelectorAll(button[type=submit]))
.find(b => b.textContent.includes(Log in))?.click();
});
await page.waitForTimeout(5000);
// 验证:应重定向到 /user/
if (page.url().includes(/login)) {
throw new Error(登录失败);
}
关键陷阱
MusicBrainz 登录页面有
两个表单:一个搜索表单和一个登录表单。两者都有 button[type=submit]。使用 page.click(button[type=submit]) 会点击搜索按钮。
始终使用上述 evaluate 模式通过文本内容查找按钮。
步骤 2:添加艺术家
何时使用: 艺术家在 MusicBrainz 上不存在。先用 mb_lookup.sh 或搜索 API 检查。
脚本结构
javascript
// ... 浏览器启动 + 登录(步骤 1)...
await page.goto(https://musicbrainz.org/artist/create, {
waitUntil: networkidle, timeout: 30000
});
await page.waitForTimeout(2000);
// 填写字段
await page.fill(#id-edit-artist\\.name, 艺术家名称);
await page.fill(#id-edit-artist\\.sort_name, 名称, 艺术家); // 姓, 名 或 名称, The
await page.selectOption(#id-edit-artist\\.type_id, 2); // 1=个人, 2=团体, 4=其他
await page.fill(#id-edit-artist\\.edit_note,
从 Spotify 添加艺术家:https://open.spotify.com/artist/);
// 提交 — 使用 form.submit() 避免按钮歧义
await page.evaluate(() => {
document.getElementById(id-edit-artist.name)?.closest(form)?.submit();
});
await page.waitForTimeout(5000);
// 从重定向 URL 提取 MBID
const artistUrl = page.url();
const artistMbid = artistUrl.match(/artist\/([a-f0-9-]{36})/)?.[1];
console.log(艺术家 MBID:, artistMbid);
await browser.close();
完成后
向用户报告艺术家 MBID 和链接。存储 MBID — 添加发行版时需要。
步骤 3:添加发行版
每个发行版都遵循此相同流程,无论之前发生了什么(登录、艺术家创建、之前的发行版)。每个都是一个全新的 Playwright 脚本。
表单字段 ID(供参考)
ac-source-single-artist | 文本(自动补全) |
| 发行组 | release-group | 文本(自动补全) |
| 主要类型 | primary-type | 1=专辑, 2=单曲, 3=EP |
| 状态 | status | 1=官方 |
| 语言 | language | 120=英语, 145=德语 |
| 文字 | script | 28=拉丁, 12=西里尔 |
| 年份 | event-date-0 | YYYY |
| 月份 | .partial-date-month | MM |
| 日期 | .partial-date-day | DD |
| 国家 | country-0 | 240=[全球] |
| 厂牌 | label-0 | 文本(自动补全) |
| 条形码 | barcode | 文本 |
| 无条形码 | no-barcode | 复选框 |
| 包装 | packaging | 7=无 |
已知陷阱
- 1. 发行组自动补全会阻塞表单。 填写标题并选择艺术家后,发行组字段会自动搜索并打开一个弹出对话框(添加介质),使所有其他表单字段对 Playwright 不可见(offsetParent = null,零大小矩形)。在与其他字段交互之前,必须关闭此对话框。
- 2. 艺术家自动补全可能匹配错误实体。 输入The Bride Wore Black并按 ArrowDown+Enter 可能会选择一个名称相似的发行组或不同的艺术家。在自动补全字段中使用 MBID 进行精确匹配。
- 3. React 选择框在不可见时不会响应 page.selectOption()。 改用带有原生设置器的 evaluate。
- 4. 曲目解析器对话框 vs 添加介质对话框。 曲目解析器在 jQuery UI 对话框中打开。可能有多个对话框同时打开。始终通过对话框标题文本定位。
- 5. 曲目解析器可能重复曲目。 解析可能会产生重复条目。解析后始终验证曲目数量。
脚本结构
javascript
// ... 浏览器启动 + 登录(步骤 1)...
// 导航到空的添加发行版表单
await page.goto(https://musicbrainz.org/release/add, {
waitUntil: networkidle, timeout: 30000
});
await page.waitForTimeout(3000);
// === 标题 ===
// 使用带有原生设置器的 evaluate — 键盘输入不稳定
await page.evaluate((title) => {
const el = document.getElementById(name);
const setter = Object.getOwnPropertyDescriptor(
window.HTMLInputElement.prototype, value
).set;
setter.call(el, title);
el.dispatchEvent(new Event(input, { bubbles: true }));
el.dispatchEvent(new Event(change, { bubbles: true }));
}, 专辑标题);
await page.waitForTimeout(500);
// === 艺术家 ===
// 粘贴 MBID 进行精确匹配(避免错误的自动补全选择)
const artist