{"url_pattern":"^https?://(www\\.)?bilibili\\.com/.*$","site_name":"bilibili","allowed_domains":["api.bilibili.com","bilibili.com","example.com","member.bilibili.com","search.bilibili.com","space.bilibili.com"],"tools":[{"name":"open_article_editor","description":"Navigate to Bilibili article writing page","inputSchema":{"type":"object","properties":{},"required":null},"handler":"() => {\n  window.location.href = 'https://member.bilibili.com/article-text/home';\n  return { success: true, message: 'Opening Bilibili article editor...' };\n}"},{"name":"insert_article","description":"Insert content into Bilibili article editor (must be on member.bilibili.com/article-text/home)","inputSchema":{"type":"object","properties":{"title":{"type":"string","description":"Article title"},"content":{"type":"string","description":"Article content (HTML)"}},"required":["content"]},"handler":"(params) => {\n  // Set title\n  if (params.title) {\n    const titleInput = document.querySelector('.article-title input') || document.querySelector('input[placeholder*=\"标题\"]');\n    if (titleInput) {\n      titleInput.value = params.title;\n      titleInput.dispatchEvent(new Event('input', { bubbles: true }));\n    }\n  }\n  // Find editor\n  const editor = document.querySelector('.ql-editor') || document.querySelector('[contenteditable=\"true\"]');\n  if (!editor) {\n    return { success: false, message: 'Editor not found. Open member.bilibili.com/article-text/home first' };\n  }\n  editor.innerHTML = params.content;\n  editor.dispatchEvent(new Event('input', { bubbles: true }));\n  return { success: true, message: 'Content inserted into Bilibili editor' };\n}"},{"name":"bilibili_search","description":"Search Bilibili videos by keyword","inputSchema":{"type":"object","properties":{"keyword":{"type":"string","description":"Search keyword"},"page":{"type":"string","description":"Page number (default: 1)"},"count":{"type":"string","description":"Results per page (default: 20, max: 50)"},"order":{"type":"string","description":"Sort order: totalrank (default), click (views), pubdate (newest), dm (danmaku), stow (favorites)"}},"required":["keyword"]},"handler":"(params) => {\n  const run = async function(args) {\n\n      if (!args.keyword) return {error: 'Missing argument: keyword'};\n      const page = parseInt(args.page) || 1;\n      const ps = Math.min(parseInt(args.count) || 20, 50);\n      const order = args.order || 'totalrank';\n      const params = new URLSearchParams({search_type: 'video', keyword: args.keyword, page: String(page), page_size: String(ps), order});\n      const resp = await fetch('https://api.bilibili.com/x/web-interface/wbi/search/type?' + params, {credentials: 'include'});\n      if (!resp.ok) return {error: 'HTTP ' + resp.status, hint: 'Not logged in?'};\n      const d = await resp.json();\n      if (d.code !== 0) return {error: d.message || 'API error ' + d.code, hint: 'Not logged in?'};\n      const stripHtml = s => (s || '').replace(/<[^>]*>/g, '');\n      const videos = (d.data?.result || []).map(r => ({\n        bvid: r.bvid,\n        title: stripHtml(r.title),\n        author: r.author,\n        author_mid: r.mid,\n        description: stripHtml(r.description).substring(0, 200),\n        duration: r.duration,\n        play: r.play,\n        danmaku: r.danmaku,\n        like: r.like,\n        favorites: r.favorites,\n        cover: r.pic?.startsWith('//') ? 'https:' + r.pic : r.pic,\n        pub_date: r.pubdate ? new Date(r.pubdate * 1000).toISOString() : null,\n        tags: r.tag || '',\n        url: 'https://www.bilibili.com/video/' + r.bvid\n      }));\n      return {keyword: args.keyword, page, total: d.data?.numResults || 0, count: videos.length, videos};\n  };\n  return run(params || {});\n}"},{"name":"bilibili_video","description":"Get Bilibili video details by bvid","inputSchema":{"type":"object","properties":{"bvid":{"type":"string","description":"Video BV ID (e.g. BV1xx411c7mD)"}},"required":["bvid"]},"handler":"(params) => {\n  const run = async function(args) {\n\n      const bvid = args.bvid || args._positional?.[0];\n      if (!bvid) return {error: 'Missing argument: bvid'};\n      const resp = await fetch('https://api.bilibili.com/x/web-interface/view?bvid=' + encodeURIComponent(bvid), {credentials: 'include'});\n      if (!resp.ok) return {error: 'HTTP ' + resp.status, hint: 'Not logged in?'};\n      const d = await resp.json();\n      if (d.code !== 0) return {error: d.message || 'API error ' + d.code, hint: d.code === -404 ? 'Video not found' : 'Not logged in?'};\n      const v = d.data;\n      const result = {\n        bvid: v.bvid,\n        aid: v.aid,\n        title: v.title,\n        description: v.desc,\n        cover: v.pic,\n        duration: v.duration,\n        duration_text: Math.floor(v.duration / 60) + ':' + String(v.duration % 60).padStart(2, '0'),\n        author: v.owner?.name,\n        author_mid: v.owner?.mid,\n        author_face: v.owner?.face,\n        category: v.tname,\n        tags: v.tag || null,\n        pub_date: v.pubdate ? new Date(v.pubdate * 1000).toISOString() : null,\n        stat: {\n          view: v.stat?.view,\n          like: v.stat?.like,\n          dislike: v.stat?.dislike,\n          coin: v.stat?.coin,\n          favorite: v.stat?.favorite,\n          share: v.stat?.share,\n          reply: v.stat?.reply,\n          danmaku: v.stat?.danmaku\n        },\n        pages: (v.pages || []).map(p => ({\n          page: p.page,\n          cid: p.cid,\n          title: p.part,\n          duration: p.duration\n        })),\n        url: 'https://www.bilibili.com/video/' + v.bvid\n      };\n\n      // Also fetch related videos\n      try {\n        const resp2 = await fetch('https://api.bilibili.com/x/web-interface/archive/related?bvid=' + encodeURIComponent(bvid), {credentials: 'include'});\n        const d2 = await resp2.json();\n        if (d2.code === 0 && d2.data) {\n          result.related = d2.data.slice(0, 5).map(r => ({\n            bvid: r.bvid,\n            title: r.title,\n            author: r.owner?.name,\n            view: r.stat?.view,\n            duration: r.duration,\n            url: 'https://www.bilibili.com/video/' + r.bvid\n          }));\n        }\n      } catch(e) {}\n\n      return result;\n  };\n  return run(params || {});\n}"},{"name":"bilibili_comments","description":"Get comments for a Bilibili video","inputSchema":{"type":"object","properties":{"bvid":{"type":"string","description":"Video BV ID"},"page":{"type":"string","description":"Page number (default: 1)"},"count":{"type":"string","description":"Comments per page (default: 20, max: 30)"},"sort":{"type":"string","description":"Sort: 0=by_time, 2=by_likes (default: 2)"}},"required":["bvid"]},"handler":"(params) => {\n  const run = async function(args) {\n\n      const bvid = args.bvid || args._positional?.[0];\n      if (!bvid) return {error: 'Missing argument: bvid'};\n      const pn = parseInt(args.page) || 1;\n      const ps = Math.min(parseInt(args.count) || 20, 30);\n      const sort = args.sort !== undefined ? parseInt(args.sort) : 2;\n\n      // First get aid from bvid\n      const viewResp = await fetch('https://api.bilibili.com/x/web-interface/view?bvid=' + encodeURIComponent(bvid), {credentials: 'include'});\n      if (!viewResp.ok) return {error: 'HTTP ' + viewResp.status, hint: 'Not logged in?'};\n      const viewData = await viewResp.json();\n      if (viewData.code !== 0) return {error: viewData.message || 'Failed to get video info', hint: viewData.code === -404 ? 'Video not found' : 'Not logged in?'};\n      const aid = viewData.data?.aid;\n\n      // Fetch comments using aid\n      const resp = await fetch('https://api.bilibili.com/x/v2/reply?type=1&oid=' + aid + '&pn=' + pn + '&ps=' + ps + '&sort=' + sort, {credentials: 'include'});\n      if (!resp.ok) return {error: 'HTTP ' + resp.status, hint: 'Not logged in?'};\n      const d = await resp.json();\n      if (d.code !== 0) return {error: d.message || 'API error ' + d.code, hint: 'Not logged in?'};\n\n      const formatReply = r => ({\n        rpid: r.rpid_str,\n        user: r.member?.uname,\n        user_mid: r.mid,\n        user_level: r.member?.level_info?.current_level,\n        content: r.content?.message,\n        like: r.like,\n        reply_count: r.rcount,\n        time: r.ctime ? new Date(r.ctime * 1000).toISOString() : null,\n        sub_replies: (r.replies || []).slice(0, 3).map(sr => ({\n          user: sr.member?.uname,\n          content: sr.content?.message,\n          like: sr.like,\n          time: sr.ctime ? new Date(sr.ctime * 1000).toISOString() : null\n        }))\n      });\n\n      const comments = (d.data?.replies || []).map(formatReply);\n\n      // Include top/pinned comments on first page\n      let top = null;\n      if (pn === 1 && d.data?.top_replies?.length) {\n        top = d.data.top_replies.map(formatReply);\n      }\n\n      return {\n        bvid,\n        aid,\n        title: viewData.data?.title,\n        page: pn,\n        total: d.data?.page?.count || 0,\n        count: comments.length,\n        sort: sort === 0 ? 'by_time' : 'by_likes',\n        top_comments: top,\n        comments\n      };\n  };\n  return run(params || {});\n}"},{"name":"bilibili_feed","description":"Get Bilibili dynamic feed (timeline from followed users)","inputSchema":{"type":"object","properties":{"type":{"type":"string","description":"Filter type: all (default), video, article, draw"},"count":{"type":"string","description":"Max items to return (default: 20)"}},"required":null},"handler":"(params) => {\n  const run = async function(args) {\n\n      const typeMap = {all: 'all', video: 'video', article: 'article', draw: 'draw'};\n      const type = typeMap[args.type] || 'all';\n      const maxCount = parseInt(args.count) || 20;\n      const resp = await fetch('https://api.bilibili.com/x/polymer/web-dynamic/v1/feed/all?type=' + type + '&page=1', {credentials: 'include'});\n      if (!resp.ok) return {error: 'HTTP ' + resp.status, hint: 'Not logged in?'};\n      const d = await resp.json();\n      if (d.code !== 0) return {error: d.message || 'API error ' + d.code, hint: 'Not logged in?'};\n      if (!d.data?.items?.length) return {error: 'No feed items', hint: 'Not logged in or not following anyone?'};\n\n      const items = (d.data.items || []).slice(0, maxCount).map(item => {\n        const author = item.modules?.module_author;\n        const dynamic = item.modules?.module_dynamic;\n        const stat = item.modules?.module_stat;\n        const base = {\n          id: item.id_str,\n          type: item.type,\n          url: 'https://www.bilibili.com/opus/' + item.id_str,\n          author: author?.name,\n          author_mid: author?.mid,\n          author_face: author?.face,\n          pub_time: author?.pub_ts ? new Date(author.pub_ts * 1000).toISOString() : null,\n          pub_action: author?.pub_action,\n          text: dynamic?.desc?.text || null,\n          comment_count: stat?.comment?.count,\n          forward_count: stat?.forward?.count,\n          like_count: stat?.like?.count\n        };\n\n        // Video type\n        if (item.type === 'DYNAMIC_TYPE_AV' && dynamic?.major?.archive) {\n          const arc = dynamic.major.archive;\n          base.video = {\n            bvid: arc.bvid,\n            title: arc.title,\n            cover: arc.cover,\n            duration_text: arc.duration_text,\n            play: arc.stat?.play,\n            danmaku: arc.stat?.danmaku,\n            url: 'https://www.bilibili.com/video/' + arc.bvid\n          };\n        }\n\n        // Draw/image type\n        if (item.type === 'DYNAMIC_TYPE_DRAW' && dynamic?.major?.draw) {\n          base.images = (dynamic.major.draw.items || []).map(img => img.src);\n        }\n\n        // Article type\n        if (item.type === 'DYNAMIC_TYPE_ARTICLE' && dynamic?.major?.article) {\n          const art = dynamic.major.article;\n          base.article = {\n            id: art.id,\n            title: art.title,\n            covers: art.covers,\n            url: 'https://www.bilibili.com/read/cv' + art.id\n          };\n        }\n\n        return base;\n      });\n\n      return {type, count: items.length, has_more: !!d.data.has_more, items};\n  };\n  return run(params || {});\n}"},{"name":"bilibili_history","description":"Get Bilibili watch history","inputSchema":{"type":"object","properties":{"count":{"type":"string","description":"Number of items (default: 20, max: 50)"}},"required":null},"handler":"(params) => {\n  const run = async function(args) {\n\n      const ps = Math.min(parseInt(args.count) || 20, 50);\n      const resp = await fetch('https://api.bilibili.com/x/web-interface/history/cursor?ps=' + ps + '&type=archive', {credentials: 'include'});\n      if (!resp.ok) return {error: 'HTTP ' + resp.status, hint: 'Not logged in?'};\n      const d = await resp.json();\n      if (d.code !== 0) return {error: d.message || 'API error ' + d.code, hint: 'Not logged in?'};\n      if (!d.data?.list) return {error: 'No history data', hint: 'Not logged in?'};\n\n      const items = (d.data.list || []).map(h => {\n        const progress = h.progress === -1 ? 'completed' : h.progress > 0 ? Math.floor(h.progress / 60) + ':' + String(h.progress % 60).padStart(2, '0') : 'not_started';\n        const duration_text = h.duration > 0 ? Math.floor(h.duration / 60) + ':' + String(h.duration % 60).padStart(2, '0') : null;\n        return {\n          bvid: h.history?.bvid,\n          title: h.title,\n          author: h.author_name,\n          author_mid: h.author_mid,\n          cover: h.cover,\n          duration: h.duration,\n          duration_text,\n          progress,\n          progress_seconds: h.progress,\n          view_at: h.view_at ? new Date(h.view_at * 1000).toISOString() : null,\n          tag_name: h.tag_name,\n          url: h.history?.bvid ? 'https://www.bilibili.com/video/' + h.history.bvid : null\n        };\n      });\n\n      return {count: items.length, items};\n  };\n  return run(params || {});\n}"},{"name":"bilibili_me","description":"Get current Bilibili logged-in user info","inputSchema":{"type":"object","properties":{},"required":null},"handler":"(params) => {\n  const run = async function(args) {\n\n      const resp = await fetch('https://api.bilibili.com/x/web-interface/nav', {credentials: 'include'});\n      if (!resp.ok) return {error: 'HTTP ' + resp.status, hint: 'Not logged in?'};\n      const d = await resp.json();\n      if (d.code !== 0) return {error: d.message || 'API error ' + d.code, hint: 'Not logged in?'};\n      if (!d.data?.isLogin) return {error: 'Not logged in', hint: 'Please log in to bilibili.com first'};\n      const u = d.data;\n      const result = {\n        mid: u.mid,\n        username: u.uname,\n        url: 'https://space.bilibili.com/' + u.mid,\n        face: u.face,\n        level: u.level_info?.current_level,\n        coins: u.money,\n        vip: u.vipType > 0,\n        vip_type: u.vipType === 1 ? 'monthly' : u.vipType === 2 ? 'annual' : 'none',\n        vip_label: u.vip_label?.text || null,\n        moral: u.moral,\n        email_verified: u.email_verified === 1,\n        tel_verified: u.mobile_verified === 1,\n        follower: null,\n        following: null\n      };\n      try {\n        const statResp = await fetch('https://api.bilibili.com/x/web-interface/nav/stat', {credentials: 'include'});\n        const statData = await statResp.json();\n        if (statData.code === 0 && statData.data) {\n          result.follower = statData.data.follower;\n          result.following = statData.data.following;\n        }\n      } catch(e) {}\n      return result;\n  };\n  return run(params || {});\n}"}]}