douban WebMCP
Browser tool configuration for douban
Tools (8)
douban_search()
Search Douban across movies, books, and music
Parameters
JavaScript Handler
(params) => {
const run = async function(args) {
if (!args.keyword) return {error: 'Missing argument: keyword'};
const q = encodeURIComponent(args.keyword);
// Try the rich search_suggest endpoint (requires www.douban.com origin)
var resp;
var usedFallback = false;
try {
resp = await fetch('https://www.douban.com/j/search_suggest?q=' + q, {credentials: 'include'});
if (!resp.ok) throw new Error('HTTP ' + resp.status);
} catch (e) {
// Fallback: use movie.douban.com subject_suggest (works cross-subdomain via same eTLD+1 cookies)
try {
resp = await fetch('/j/subject_suggest?q=' + q, {credentials: 'include'});
usedFallback = true;
} catch (e2) {
return {error: 'Search failed: ' + e2.message, hint: 'Not logged in? Navigate to www.douban.com first.'};
}
}
if (!resp.ok) return {error: 'HTTP ' + resp.status, hint: 'Not logged in?'};
var d = await resp.json();
if (usedFallback) {
// subject_suggest returns an array directly
var items = (Array.isArray(d) ? d : []).map(function(c, i) {
return {
rank: i + 1,
id: c.id,
type: c.type === 'movie' ? 'movie' : c.type === 'b' ? 'book' : c.type || 'unknown',
title: c.title,
subtitle: c.sub_title || '',
rating: null,
info: '',
year: c.year || null,
cover: c.img || c.pic || null,
url: c.url
};
});
return {
keyword: args.keyword,
count: items.length,
results: items,
suggestions: [],
note: 'Limited results (movie/book only). For richer results, navigate to www.douban.com first.'
};
}
// Rich search_suggest response with cards
var cards = (d.cards || []).map(function(c, i) {
var id = c.url && c.url.match(/subject\/(\d+)/);
id = id ? id[1] : null;
var ratingMatch = c.card_subtitle && c.card_subtitle.match(/([\d.]+)分/);
return {
rank: i + 1,
id: id,
type: c.type || 'unknown',
title: c.title,
subtitle: c.abstract || '',
rating: ratingMatch ? parseFloat(ratingMatch[1]) : null,
info: c.card_subtitle || '',
year: c.year || null,
cover: c.cover_url || null,
url: c.url
};
});
var suggestions = d.words || [];
return {
keyword: args.keyword,
count: cards.length,
results: cards,
suggestions: suggestions
};
};
return run(params || {});
}
douban_search_books()
Navigate to Douban book search results
Parameters
JavaScript Handler
(params) => {
window.location.href = 'https://search.douban.com/book/subject_search?search_text=' + encodeURIComponent(params.query);
return { success: true, message: 'Opening Douban book search...', query: params.query };
}
douban_get_book_details()
Extract book details from the current Douban subject page
Parameters
No parameters
JavaScript Handler
() => {
const title = document.querySelector('#wrapper h1 span')?.textContent?.trim() || document.title;
const ratingText = document.querySelector('strong[property="v:average"]')?.textContent?.trim() || '';
const introBlocks = Array.from(document.querySelectorAll('#link-report .intro, .related_info .intro'))
.map((node) => node.textContent?.trim())
.filter(Boolean);
const infoText = document.querySelector('#info')?.textContent?.replace(/\s+/g, ' ').trim() || '';
const cover = document.querySelector('#mainpic img')?.src || null;
const metadata = {};
infoText.split(/\s{2,}|\n/).forEach((line) => {
const parts = line.split(':');
if (parts.length >= 2) {
const key = parts[0].trim();
const value = parts.slice(1).join(':').trim();
if (key && value) metadata[key] = value;
}
});
return {
success: true,
book: {
title,
rating: ratingText ? Number(ratingText) : null,
metadata,
intro: introBlocks.join('\n\n'),
cover,
url: window.location.href
}
};
}
douban_movie()
Get detailed movie/TV info with rating, cast, and hot reviews from Douban
Parameters
JavaScript Handler
(params) => {
const run = async function(args) {
if (!args.id) return {error: 'Missing argument: id'};
const id = String(args.id).trim();
// Fetch structured data from the JSON API
const apiResp = await fetch('https://movie.douban.com/j/subject_abstract?subject_id=' + id, {credentials: 'include'});
if (!apiResp.ok) return {error: 'HTTP ' + apiResp.status, hint: 'Not logged in or invalid ID?'};
const apiData = await apiResp.json();
if (apiData.r !== 0 || !apiData.subject) return {error: 'Subject not found', hint: 'Check the ID'};
const s = apiData.subject;
// Also fetch the HTML page for richer data (summary, rating distribution, hot comments)
const pageResp = await fetch('https://movie.douban.com/subject/' + id + '/', {credentials: 'include'});
let summary = '', ratingDist = {}, hotComments = [], recommendations = [], votes = null, info = '';
if (pageResp.ok) {
const html = await pageResp.text();
const doc = new DOMParser().parseFromString(html, 'text/html');
// Summary
const summaryEl = doc.querySelector('[property="v:summary"]');
summary = summaryEl ? summaryEl.textContent.trim() : '';
// Vote count
const votesEl = doc.querySelector('[property="v:votes"]');
votes = votesEl ? parseInt(votesEl.textContent) : null;
// Info block
const infoEl = doc.querySelector('#info');
info = infoEl ? infoEl.innerText || infoEl.textContent.trim() : '';
// Rating distribution
doc.querySelectorAll('.ratings-on-weight .item').forEach(function(el) {
var star = el.querySelector('span:first-child');
var pct = el.querySelector('.rating_per');
if (star && pct) ratingDist[star.textContent.trim()] = pct.textContent.trim();
});
// Hot comments
doc.querySelectorAll('#hot-comments .comment-item').forEach(function(el) {
var author = el.querySelector('.comment-info a');
var rating = el.querySelector('.comment-info .rating');
var content = el.querySelector('.short');
var voteCount = el.querySelector('.vote-count');
var date = el.querySelector('.comment-time');
hotComments.push({
author: author ? author.textContent.trim() : '',
rating: rating ? rating.title : '',
content: content ? content.textContent.trim() : '',
votes: voteCount ? parseInt(voteCount.textContent) || 0 : 0,
date: date ? date.textContent.trim() : ''
});
});
// Recommendations
doc.querySelectorAll('.recommendations-bd dl').forEach(function(dl) {
var a = dl.querySelector('dd a');
if (a) {
var recId = a.href?.match(/subject\/(\d+)/)?.[1];
recommendations.push({title: a.textContent.trim(), id: recId, url: a.href});
}
});
}
// Parse info block for structured fields
const parseInfo = function(text) {
const result = {};
const lines = text.split('\n').map(function(l) { return l.trim(); }).filter(Boolean);
lines.forEach(function(line) {
var m = line.match(/^(.+?):\s*(.+)$/);
if (m) result[m[1].trim()] = m[2].trim();
});
return result;
};
const infoFields = parseInfo(info);
return {
id: s.id,
title: s.title,
subtype: s.subtype,
is_tv: s.is_tv,
rating: parseFloat(s.rate) || null,
votes: votes,
rating_distribution: ratingDist,
directors: s.directors,
actors: s.actors,
types: s.types,
region: s.region,
duration: s.duration,
release_year: s.release_year,
episodes_count: s.episodes_count || null,
imdb: infoFields['IMDb'] || null,
alias: infoFields['又名'] || null,
language: infoFields['语言'] || null,
release_date: infoFields['上映日期'] || infoFields['首播'] || null,
summary: summary,
playable: s.playable,
url: s.url,
hot_comments: hotComments,
recommendations: recommendations
};
};
return run(params || {});
}
douban_movie_hot()
Get hot/trending movies or TV shows on Douban by tag
Parameters
JavaScript Handler
(params) => {
const run = async function(args) {
const type = (args.type || 'movie').toLowerCase();
if (type !== 'movie' && type !== 'tv') return {error: 'Invalid type. Use "movie" or "tv"'};
const tag = args.tag || '热门';
const count = Math.min(parseInt(args.count) || 20, 50);
const url = 'https://movie.douban.com/j/search_subjects?type=' + type
+ '&tag=' + encodeURIComponent(tag)
+ '&page_limit=' + count
+ '&page_start=0';
const resp = await fetch(url, {credentials: 'include'});
if (!resp.ok) return {error: 'HTTP ' + resp.status, hint: 'Not logged in?'};
const d = await resp.json();
if (!d.subjects) return {error: 'No data returned', hint: 'Invalid tag or not logged in?'};
const items = d.subjects.map(function(s, i) {
return {
rank: i + 1,
id: s.id,
title: s.title,
rating: s.rate ? parseFloat(s.rate) : null,
cover: s.cover,
url: s.url,
playable: s.playable,
is_new: s.is_new,
episodes_info: s.episodes_info || null
};
});
// Also fetch available tags for reference
var tagsResp = await fetch('https://movie.douban.com/j/search_tags?type=' + type + '&source=index', {credentials: 'include'});
var availableTags = [];
if (tagsResp.ok) {
var tagsData = await tagsResp.json();
availableTags = tagsData.tags || [];
}
return {
type: type,
tag: tag,
count: items.length,
available_tags: availableTags,
items: items
};
};
return run(params || {});
}
douban_subject()
获取电影详情
Parameters
JavaScript Handler
(params) => {
const args = Object.assign({}, params || {});
let data = null;
let __wbContextUrl = globalThis.location?.href || '';
let __wbContextDocument = globalThis.document || null;
const __wbDefault = (value, fallback) => (value === undefined || value === null || value === '' ? fallback : value);
const __wbJoin = (value, separator) => Array.isArray(value) ? value.join(separator) : (value ?? '');
const __wbGet = (value, path) => {
if (!path) return value;
return String(path).split('.').reduce((acc, part) => (acc == null ? undefined : acc[part]), value);
};
const __wbBaseUrl = () => (__wbContextUrl || globalThis.location?.href || 'https://example.com/');
const __wbResolve = (target, base) => {
if (target == null) return '';
const text = String(target);
try {
return /^https?:/i.test(text) ? text : new URL(text, base || __wbBaseUrl()).toString();
} catch (_error) {
return text;
}
};
const __wbFetch = async (target, options) => {
const url = __wbResolve(target, __wbBaseUrl());
const resp = await globalThis.fetch(url, Object.assign({ credentials: 'include' }, options || {}));
if (!resp.ok) {
throw new Error(`HTTP ${resp.status} for ${url}`);
}
const text = await resp.text();
try {
return { url, text, data: JSON.parse(text) };
} catch (_error) {
return { url, text, data: text };
}
};
return (async () => {
{
const __wbResp = await __wbFetch(`https://movie.douban.com/subject/${args.id}`);
__wbContextUrl = __wbResp.url;
__wbContextDocument = new DOMParser().parseFromString(__wbResp.text, 'text/html');
data = __wbContextDocument;
}
{
const document = __wbContextDocument || globalThis.document;
const location = new URL(__wbBaseUrl());
const window = { document, location };
const fetch = (target, options) => globalThis.fetch(__wbResolve(target, __wbBaseUrl()), Object.assign({ credentials: 'include' }, options || {}));
data = await (((async () => {
const id = '(args.id)';
// Wait for page to load
await new Promise(r => setTimeout(r, 2000));
// Extract title
const titleEl = document.querySelector('span[property="v:itemreviewed"]');
const title = titleEl?.textContent?.trim() || '';
// Extract original title
const ogTitleEl = document.querySelector('span[property="v:originalTitle"]');
const originalTitle = ogTitleEl?.textContent?.trim() || '';
// Extract year
const yearEl = document.querySelector('.year');
const year = yearEl?.textContent?.trim() || '';
// Extract rating
const ratingEl = document.querySelector('strong[property="v:average"]');
const rating = parseFloat(ratingEl?.textContent || '0');
// Extract rating count
const ratingCountEl = document.querySelector('span[property="v:votes"]');
const ratingCount = parseInt(ratingCountEl?.textContent || '0', 10);
// Extract genres
const genreEls = document.querySelectorAll('span[property="v:genre"]');
const genres = Array.from(genreEls).map(el => el.textContent?.trim()).filter(Boolean).join(',');
// Extract directors
const directorEls = document.querySelectorAll('a[rel="v:directedBy"]');
const directors = Array.from(directorEls).map(el => el.textContent?.trim()).filter(Boolean).join(',');
// Extract casts
const castEls = document.querySelectorAll('a[rel="v:starring"]');
const casts = Array.from(castEls).slice(0, 5).map(el => el.textContent?.trim()).filter(Boolean).join(',');
// Extract summary
const summaryEl = document.querySelector('span[property="v:summary"]');
const summary = summaryEl?.textContent?.trim() || '';
return [{
id,
title,
originalTitle,
year,
rating,
ratingCount,
genres,
directors,
casts,
summary: summary.substring(0, 200),
url: `https://movie.douban.com/subject/${id}`
}];
})())());
}
return data;
})();
}
douban_top250()
Get Douban Top 250 movies list
Parameters
JavaScript Handler
(params) => {
const run = async function(args) {
const start = parseInt(args.start) || 0;
const resp = await fetch('https://movie.douban.com/top250?start=' + start, {credentials: 'include'});
if (!resp.ok) return {error: 'HTTP ' + resp.status, hint: 'Not logged in?'};
const html = await resp.text();
const doc = new DOMParser().parseFromString(html, 'text/html');
const items = [];
doc.querySelectorAll('.grid_view .item').forEach(function(el) {
var rank = el.querySelector('.pic em');
var titleEl = el.querySelector('.hd a .title');
var otherTitleEl = el.querySelector('.hd a .other');
var ratingEl = el.querySelector('.rating_num');
var link = el.querySelector('.hd a');
var quoteEl = el.querySelector('.quote .inq') || el.querySelector('.quote span');
var infoEl = el.querySelector('.bd p');
// Vote count is in a span like "3268455人评价"
var voteSpans = el.querySelectorAll('.bd div span');
var votes = null;
voteSpans.forEach(function(sp) {
var m = sp.textContent.match(/(\d+)人评价/);
if (m) votes = parseInt(m[1]);
});
var id = link?.href?.match(/subject\/(\d+)/)?.[1];
// Parse info line for director, year, region, genre
var infoText = infoEl ? infoEl.textContent.trim() : '';
var lines = infoText.split('\n').map(function(l) { return l.trim(); }).filter(Boolean);
var directorLine = lines[0] || '';
var metaLine = lines[1] || '';
var metaParts = metaLine.split('/').map(function(p) { return p.trim(); });
items.push({
rank: rank ? parseInt(rank.textContent) : null,
id: id,
title: titleEl ? titleEl.textContent.trim() : '',
other_title: otherTitleEl ? otherTitleEl.textContent.trim().replace(/^\s*\/\s*/, '') : '',
rating: ratingEl ? parseFloat(ratingEl.textContent) : null,
votes: votes,
quote: quoteEl ? quoteEl.textContent.trim() : '',
year: metaParts[0] || '',
region: metaParts[1] || '',
genre: metaParts[2] || '',
url: link ? link.href : ''
});
});
return {
start: start,
count: items.length,
total: 250,
has_more: start + items.length < 250,
next_start: start + items.length < 250 ? start + 25 : null,
items: items
};
};
return run(params || {});
}
douban_comments()
Get short reviews/comments for a Douban movie or TV show
Parameters
JavaScript Handler
(params) => {
const run = async function(args) {
if (!args.id) return {error: 'Missing argument: id'};
const id = String(args.id).trim();
const sort = args.sort || 'new_score';
const count = Math.min(parseInt(args.count) || 20, 50);
if (sort !== 'new_score' && sort !== 'time') {
return {error: 'Invalid sort. Use "new_score" (hot) or "time" (newest)'};
}
const url = 'https://movie.douban.com/j/subject/' + id + '/comments?start=0&limit=' + count + '&status=P&sort=' + sort;
const resp = await fetch(url, {credentials: 'include'});
if (!resp.ok) return {error: 'HTTP ' + resp.status, hint: 'Not logged in?'};
const d = await resp.json();
if (d.retcode !== 1 || !d.result) return {error: 'Failed to fetch comments', hint: 'Invalid ID or not logged in?'};
const ratingMap = {'1': '很差', '2': '较差', '3': '还行', '4': '推荐', '5': '力荐'};
const comments = (d.result.normal || []).map(function(c) {
var userId = c.user?.path?.match(/people\/([^/]+)/)?.[1];
return {
id: c.id,
author: c.user?.name || '',
author_id: userId || '',
rating: c.rating ? parseInt(c.rating) : null,
rating_label: c.rating_word || ratingMap[c.rating] || '',
content: c.content || '',
votes: c.votes || 0,
date: c.time || ''
};
});
return {
subject_id: id,
sort: sort,
total: d.result.total_num || 0,
count: comments.length,
comments: comments
};
};
return run(params || {});
}
🔌 Chrome MCP Server Extension
Use these tools with Claude, ChatGPT, and other AI assistants.
How to Use WebMCP
WebMCP tools are designed for browser extensions or automation frameworks. The browser extension matches the current URL against the pattern and executes the JavaScript handler when the tool is invoked.
API Endpoint:
GET /api/webmcp/match?url=https://www.douban.com/...