WORLDBOOK

Worldbooks | WebMCP | Search | Submit WebMCP

douban WebMCP

Browser tool configuration for douban

URL Pattern: ^https?://([a-z0-9-]+\.)?douban\.com(/.*)?$
Allowed Extra Domains: douban.com, example.com, movie.douban.com, search.douban.com

Tools (8)

douban_search()

Search Douban across movies, books, and music

Parameters

keyword string required - Search keyword (Chinese or English)

JavaScript Handler

douban_search_books()

Navigate to Douban book search results

Parameters

query string required - Book search keywords

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

id string required - Douban subject ID (e.g. 1292052 for The Shawshank Redemption)

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

type string - Type: movie (default) or tv
tag string - Tag filter (default: 热门). Movies: 热门/最新/豆瓣高分/冷门佳片/华语/欧美/韩国/日本. TV: 热门/国产剧/综艺/美剧/日剧/韩剧/日本动画/纪录片
count string - Number of results (default: 20, max: 50)

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

id string required - 电影 ID

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

start string - Start position (default: 0, step by 25). Use 0 for #1-25, 25 for #26-50, etc.

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

id string required - Douban subject ID (e.g. 1292052)
sort string - Sort order: new_score (default, hot), time (newest first)
count string - Number of comments (default: 20, max: 50)

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.

Get Extension →

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/...