Archive Behance projects to Eagle DAM (Digital Asset Management) library. Use when user wants to archive or save a Behance project URL to their Eagle collection with proper metadata. Triggers include requests like '归档 https://www.behance.net/gallery/...', '保存 Behance 项目', 'archive behance project', or any request to download or save Behance gallery content to local Eagle library.
Archive Behance projects to Eagle DAM (Digital Asset Management) library. Use when user wants to archive or save a Behance project URL to their Eagle collection with proper metadata. Triggers include requests like '归档 https://www.behance.net/gallery/...', '保存 Behance 项目', 'archive behance project', or any request to download or save Behance gallery content to local Eagle library.
argument-hint
<Behance project URL>
disable-model-invocation
true
Archive Behance
Archive Behance projects to Eagle DAM library with proper folder structure and metadata.
Create project folder with sanitized name (slug from URL or project title)
Download images and create Eagle metadata:
Use original image URL from mir-s3-cdn-cf.behance.net
Name: use image alt text or generate sequential name
URL: image source URL (permanent link)
Tags: optional, can be empty
Provide summary to user with download statistics
Browser Access
Use Playwright MCP (mcp__plugin_playwright_playwright__browser_navigate) to access Behance pages.
Never write Python/shell scripts that call Playwright directly.
Extracting Project Data
Use JavaScript evaluation to extract:
// Get project info and images
() => {
const images = [];
document.querySelectorAll('img').forEach((img, i) => {
if (img.src && img.src.includes('mir-s3-cdn')) {
images.push({
src: img.src,
alt: img.alt || '',
width: img.width,
height: img.height
});
}
});
// Filter to main project images only (exclude thumbnails and avatars)const mainImages = images.filter(img =>
img.src.includes('project_modules') &&
!img.src.includes('/projects/404/')
);
return {
title: document.querySelector('h1')?.textContent?.trim() || '',
creativeField: document.querySelector('a[href*="field="]')?.textContent?.trim() || '',
tags: Array.from(document.querySelectorAll('a[href*="tracking_source=project_tag"]'))
.map(t => t.textContent.trim()),
images: mainImages
};
}
Finding Target Folder in metadata.json
Important: metadata.json can be very large (100k+ tokens). Never read the entire file into memory.
Method 1: Using grep (Recommended)
Use grep to extract just the folder ID without loading the entire file:
import subprocess
import json
from pathlib import Path
deffind_folder_id_by_name(library_root: Path, folder_name: str) -> str:
"""
Find folder ID by name using grep (memory efficient).
Returns folder ID or None if not found.
"""
metadata_path = library_root / "metadata.json"# Use grep to find the line with the folder name
result = subprocess.run(
['grep', '-B', '5', f'"name": "{folder_name}"', str(metadata_path)],
capture_output=True, text=True
)
if result.returncode != 0:
returnNone# Parse the output to find IDfor line in result.stdout.split('\n'):
if'"id":'in line:
# Extract ID from "id": "ABC123"import re
match = re.search(r'"id":\s*"([^"]+)"', line)
ifmatch:
returnmatch.group(1)
returnNone# Usage: Find "图形设计" folder ID
folder_id = find_folder_id_by_name(Path("."), "图形设计")
Method 2: Using ijson (Streaming Parser)
For complex searches through nested structures, use ijson to stream-parse:
import ijson
from pathlib import Path
deffind_behance_folder(library_root: Path, creative_field: str) -> str:
"""
Find Behance subfolder ID using streaming JSON parser.
Memory efficient for large metadata files.
"""
metadata_path = library_root / "metadata.json"
field_map = {
"Illustration": "插图",
"Graphic Design": "图形设计",
"Photography": "摄影",
"UI/UX": "UI/UX",
"Motion Graphics": "动画",
"Typography": "字体设计",
"Branding": "图形设计",
"3D Art": "3D Art",
"Architecture": "建筑",
"Fashion": "时尚",
"Advertising": "广告",
"Fine Arts": "美术",
"Crafts": "手工艺",
"Game Design": "游戏设计",
}
target_name = field_map.get(creative_field, "未分类")
withopen(metadata_path, 'rb') as f:
# Stream through foldersfor folder in ijson.items(f, 'folders.item'):
if folder.get('name') == 'Collections':
for child in folder.get('children', []):
if child.get('name') == 'Behance':
for subfolder in child.get('children', []):
if subfolder.get('name') == target_name:
return subfolder['id']
returnNone
Problem: Thumbnail visible but original file won't open.
Cause: Filename doesn't match metadata name field.
Wrong:
# metadata.json: "name": "New raft new river - 26"# Actual file: KldZIybF9RPGJ.jpg ❌ Eagle can't find it
Correct:
# metadata.json: "name": "New raft new river - 26"# Actual file: New raft new river - 26.jpg ✅ Matches name field
Missing Thumbnail
Problem: Resources invisible in grid view.
Required file structure:
KldZIybF9RPGJ.info/
├── New raft new river - 26.jpg # Original image (matches "name" field)
├── metadata.json # Metadata
└── New raft new river - 26_thumbnail.png # Thumbnail (matches filename)
Outdated mtime.json
Problem: Eagle can't find new resources.
Fix: Always rebuild index after adding resources.
Project Folder Not Saved
Problem: Resources downloaded but not visible in Eagle. Folder appears to be created but doesn't exist in metadata.json.
Cause: Folder created in memory but not properly persisted to metadata.json, or saved to wrong location in the JSON tree.
Correct Implementation:
defcreate_project_folder(library_root: Path, parent_folder_id: str,
project_name: str) -> str:
"""
Create project folder in metadata.json with verification.
Returns the new folder ID.
"""import json
import random
import string
from datetime import datetime
from pathlib import Path
defgenerate_folder_id():
chars = string.ascii_uppercase + string.ascii_lowercase + string.digits
return''.join(random.choices(chars, k=13))
metadata_path = library_root / "metadata.json"# Read current metadatawithopen(metadata_path, 'r', encoding='utf-8') as f:
metadata = json.load(f)
# Generate folder
folder_id = generate_folder_id()
now_ms = int(datetime.now().timestamp() * 1000)
new_folder = {
"id": folder_id,
"name": project_name,
"description": "",
"children": [],
"modificationTime": now_ms,
"tags": [],
"password": "",
"passwordTips": ""
}
# Find and update parent folder
folder_added = Falsefor folder in metadata.get("folders", []):
if folder["name"] == "Collections":
for child in folder.get("children", []):
if child["name"] == "Behance":
for sub in child.get("children", []):
if sub["id"] == parent_folder_id:
sub.setdefault("children", []).append(new_folder)
folder_added = Trueprint(f"Added to: Collections > Behance > {sub['name']}")
breakif folder_added:
breakif folder_added:
breakifnot folder_added:
raise ValueError(f"Parent folder {parent_folder_id} not found!")
# CRITICAL: Verify before saving# Write to temp file first
temp_path = metadata_path.with_suffix('.tmp')
withopen(temp_path, 'w', encoding='utf-8') as f:
json.dump(metadata, f, ensure_ascii=False, indent=2)
# Atomic rename
temp_path.replace(metadata_path)
# CRITICAL: Verify the save workedwithopen(metadata_path, 'r', encoding='utf-8') as f:
verify = json.load(f)
folder_found = Falsefor folder in verify.get("folders", []):
if folder["name"] == "Collections":
for child in folder.get("children", []):
if child["name"] == "Behance":
for sub in child.get("children", []):
for proj in sub.get("children", []):
if proj["id"] == folder_id:
folder_found = Truebreakifnot folder_found:
raise RuntimeError(f"Folder {folder_id} not found after save!")
print(f"✅ Folder created and verified: {project_name} (ID: {folder_id})")
return folder_id
Verification Checklist:
✅ Parent folder ID exists in metadata.json
✅ New folder added to correct parent's children array
✅ File saved atomically (temp file → rename)
✅ Re-read and verify folder exists after save
✅ Only proceed with downloads after folder verification