{"url_pattern":"^https?://linux\\.do(/.*)?$","site_name":"linux-do","allowed_domains":["example.com","linux.do"],"tools":[{"name":"linux_do_categories","description":"linux.do 分类列表","inputSchema":{"type":"object","properties":{"subcategories":{"type":"boolean","description":"Include subcategories","default":false},"limit":{"type":"number","description":"Number of categories","default":20}},"required":null},"handler":"(params) => {\n  const args = Object.assign({\"subcategories\": false, \"limit\": 20}, params || {});\n  let data = null;\n  let __wbContextUrl = globalThis.location?.href || '';\n  let __wbContextDocument = globalThis.document || null;\n    const __wbDefault = (value, fallback) => (value === undefined || value === null || value === '' ? fallback : value);\n    const __wbJoin = (value, separator) => Array.isArray(value) ? value.join(separator) : (value ?? '');\n    const __wbGet = (value, path) => {\n      if (!path) return value;\n      return String(path).split('.').reduce((acc, part) => (acc == null ? undefined : acc[part]), value);\n    };\n    const __wbBaseUrl = () => (__wbContextUrl || globalThis.location?.href || 'https://example.com/');\n    const __wbResolve = (target, base) => {\n      if (target == null) return '';\n      const text = String(target);\n      try {\n        return /^https?:/i.test(text) ? text : new URL(text, base || __wbBaseUrl()).toString();\n      } catch (_error) {\n        return text;\n      }\n    };\n    const __wbFetch = async (target, options) => {\n      const url = __wbResolve(target, __wbBaseUrl());\n      const resp = await globalThis.fetch(url, Object.assign({ credentials: 'include' }, options || {}));\n      if (!resp.ok) {\n        throw new Error(`HTTP ${resp.status} for ${url}`);\n      }\n      const text = await resp.text();\n      try {\n        return { url, text, data: JSON.parse(text) };\n      } catch (_error) {\n        return { url, text, data: text };\n      }\n    };\n  return (async () => {\n    {\n      const __wbResp = await __wbFetch(\"https://linux.do\");\n      __wbContextUrl = __wbResp.url;\n      __wbContextDocument = new DOMParser().parseFromString(__wbResp.text, 'text/html');\n      data = __wbContextDocument;\n    }\n    {\n      const document = __wbContextDocument || globalThis.document;\n      const location = new URL(__wbBaseUrl());\n      const window = { document, location };\n      const fetch = (target, options) => globalThis.fetch(__wbResolve(target, __wbBaseUrl()), Object.assign({ credentials: 'include' }, options || {}));\n      data = await (((async () => {\n  const res = await fetch('/categories.json', { credentials: 'include' });\n  if (!res.ok) throw new Error('HTTP ' + res.status + ' - 请先登录 linux.do');\n  let data;\n  try { data = await res.json(); } catch { throw new Error('响应不是有效 JSON - 请先登录 linux.do'); }\n  const cats = data?.category_list?.categories || [];\n  const showSub = (args.subcategories);\n  const results = [];\n  const limit = (args.limit);\n  for (const c of cats.slice(0, (args.limit))) {\n    results.push({\n      name: c.name,\n      slug: c.slug,\n      id: c.id,\n      topics: c.topic_count,\n      description: (c.description_text || '').slice(0, 80),\n    });\n    if (results.length >= limit) break;\n    if (showSub && c.subcategory_ids && c.subcategory_ids.length > 0) {\n      const subRes = await fetch('/categories.json?parent_category_id=' + c.id, { credentials: 'include' });\n      if (subRes.ok) {\n        let subData;\n        try { subData = await subRes.json(); } catch { continue; }\n        const subCats = subData?.category_list?.categories || [];\n        for (const sc of subCats) {\n          results.push({\n            name: c.name + ' / ' + sc.name,\n            slug: sc.slug,\n            id: sc.id,\n            topics: sc.topic_count,\n            description: (sc.description_text || '').slice(0, 80),\n          });\n          if (results.length >= limit) break;\n        }\n      }\n    }\n    if (results.length >= limit) break;\n  }\n  return results;\n})())());\n    }\n    {\n      const source = Array.isArray(data) ? data : (data == null ? [] : [data]);\n      data = source.map((item, index) => ({\n        \"name\": item.name,\n        \"slug\": item.slug,\n        \"id\": item.id,\n        \"topics\": item.topics,\n        \"description\": item.description,\n      }));\n    }\n    return data;\n  })();\n}"},{"name":"linux_do_search","description":"搜索 linux.do","inputSchema":{"type":"object","properties":{"query":{"type":"string","description":"Search query"},"limit":{"type":"number","description":"Number of results","default":20}},"required":["query"]},"handler":"(params) => {\n  const args = Object.assign({\"limit\": 20}, params || {});\n  let data = null;\n  let __wbContextUrl = globalThis.location?.href || '';\n  let __wbContextDocument = globalThis.document || null;\n    const __wbDefault = (value, fallback) => (value === undefined || value === null || value === '' ? fallback : value);\n    const __wbJoin = (value, separator) => Array.isArray(value) ? value.join(separator) : (value ?? '');\n    const __wbGet = (value, path) => {\n      if (!path) return value;\n      return String(path).split('.').reduce((acc, part) => (acc == null ? undefined : acc[part]), value);\n    };\n    const __wbBaseUrl = () => (__wbContextUrl || globalThis.location?.href || 'https://example.com/');\n    const __wbResolve = (target, base) => {\n      if (target == null) return '';\n      const text = String(target);\n      try {\n        return /^https?:/i.test(text) ? text : new URL(text, base || __wbBaseUrl()).toString();\n      } catch (_error) {\n        return text;\n      }\n    };\n    const __wbFetch = async (target, options) => {\n      const url = __wbResolve(target, __wbBaseUrl());\n      const resp = await globalThis.fetch(url, Object.assign({ credentials: 'include' }, options || {}));\n      if (!resp.ok) {\n        throw new Error(`HTTP ${resp.status} for ${url}`);\n      }\n      const text = await resp.text();\n      try {\n        return { url, text, data: JSON.parse(text) };\n      } catch (_error) {\n        return { url, text, data: text };\n      }\n    };\n  return (async () => {\n    {\n      const __wbResp = await __wbFetch(\"https://linux.do\");\n      __wbContextUrl = __wbResp.url;\n      __wbContextDocument = new DOMParser().parseFromString(__wbResp.text, 'text/html');\n      data = __wbContextDocument;\n    }\n    {\n      const document = __wbContextDocument || globalThis.document;\n      const location = new URL(__wbBaseUrl());\n      const window = { document, location };\n      const fetch = (target, options) => globalThis.fetch(__wbResolve(target, __wbBaseUrl()), Object.assign({ credentials: 'include' }, options || {}));\n      data = await (((async () => {\n  const keyword = ((args.query));\n  const res = await fetch('/search.json?q=' + encodeURIComponent(keyword), { credentials: 'include' });\n  if (!res.ok) throw new Error('HTTP ' + res.status + ' - 请先登录 linux.do');\n  let data;\n  try { data = await res.json(); } catch { throw new Error('响应不是有效 JSON - 请先登录 linux.do'); }\n  const topics = data?.topics || [];\n  return topics.slice(0, (args.limit)).map(t => ({\n    title: t.title,\n    views: t.views,\n    likes: t.like_count,\n    replies: (t.posts_count || 1) - 1,\n    url: 'https://linux.do/t/topic/' + t.id,\n  }));\n})())());\n    }\n    {\n      const source = Array.isArray(data) ? data : (data == null ? [] : [data]);\n      data = source.map((item, index) => ({\n        \"rank\": index + 1,\n        \"title\": item.title,\n        \"views\": item.views,\n        \"likes\": item.likes,\n        \"replies\": item.replies,\n        \"url\": item.url,\n      }));\n    }\n    if (Array.isArray(data)) data = data.slice(0, Number(args.limit) || 0);\n    return data;\n  })();\n}"},{"name":"linux_do_tags","description":"linux.do 标签列表","inputSchema":{"type":"object","properties":{"limit":{"type":"number","description":"Number of tags","default":30}},"required":null},"handler":"(params) => {\n  const args = Object.assign({\"limit\": 30}, params || {});\n  let data = null;\n  let __wbContextUrl = globalThis.location?.href || '';\n  let __wbContextDocument = globalThis.document || null;\n    const __wbDefault = (value, fallback) => (value === undefined || value === null || value === '' ? fallback : value);\n    const __wbJoin = (value, separator) => Array.isArray(value) ? value.join(separator) : (value ?? '');\n    const __wbGet = (value, path) => {\n      if (!path) return value;\n      return String(path).split('.').reduce((acc, part) => (acc == null ? undefined : acc[part]), value);\n    };\n    const __wbBaseUrl = () => (__wbContextUrl || globalThis.location?.href || 'https://example.com/');\n    const __wbResolve = (target, base) => {\n      if (target == null) return '';\n      const text = String(target);\n      try {\n        return /^https?:/i.test(text) ? text : new URL(text, base || __wbBaseUrl()).toString();\n      } catch (_error) {\n        return text;\n      }\n    };\n    const __wbFetch = async (target, options) => {\n      const url = __wbResolve(target, __wbBaseUrl());\n      const resp = await globalThis.fetch(url, Object.assign({ credentials: 'include' }, options || {}));\n      if (!resp.ok) {\n        throw new Error(`HTTP ${resp.status} for ${url}`);\n      }\n      const text = await resp.text();\n      try {\n        return { url, text, data: JSON.parse(text) };\n      } catch (_error) {\n        return { url, text, data: text };\n      }\n    };\n  return (async () => {\n    {\n      const __wbResp = await __wbFetch(\"https://linux.do\");\n      __wbContextUrl = __wbResp.url;\n      __wbContextDocument = new DOMParser().parseFromString(__wbResp.text, 'text/html');\n      data = __wbContextDocument;\n    }\n    {\n      const document = __wbContextDocument || globalThis.document;\n      const location = new URL(__wbBaseUrl());\n      const window = { document, location };\n      const fetch = (target, options) => globalThis.fetch(__wbResolve(target, __wbBaseUrl()), Object.assign({ credentials: 'include' }, options || {}));\n      data = await (((async () => {\n  const res = await fetch('/tags.json', { credentials: 'include' });\n  if (!res.ok) throw new Error('HTTP ' + res.status + ' - 请先登录 linux.do');\n  let data;\n  try { data = await res.json(); } catch { throw new Error('响应不是有效 JSON - 请先登录 linux.do'); }\n  let tags = data?.tags || [];\n  tags.sort((a, b) => (b.count || 0) - (a.count || 0));\n  return tags.slice(0, (args.limit)).map(t => ({\n    id: t.id,\n    name: t.name || t.id,\n    slug: t.slug,\n    count: t.count || 0,\n  }));\n})())());\n    }\n    {\n      const source = Array.isArray(data) ? data : (data == null ? [] : [data]);\n      data = source.map((item, index) => ({\n        \"rank\": index + 1,\n        \"name\": item.name,\n        \"count\": item.count,\n        \"slug\": item.slug,\n        \"id\": item.id,\n        \"url\": `https://linux.do/tag/${item.slug}`,\n      }));\n    }\n    return data;\n  })();\n}"},{"name":"linux_do_topic","description":"linux.do 帖子详情和回复（首页）","inputSchema":{"type":"object","properties":{"id":{"type":"number","description":"Topic ID"},"limit":{"type":"number","description":"Number of posts","default":20},"main_only":{"type":"boolean","description":"Only return the main post body without truncation","default":false}},"required":["id"]},"handler":"(params) => {\n  const args = Object.assign({\"limit\": 20, \"main_only\": false}, params || {});\n  let data = null;\n  let __wbContextUrl = globalThis.location?.href || '';\n  let __wbContextDocument = globalThis.document || null;\n    const __wbDefault = (value, fallback) => (value === undefined || value === null || value === '' ? fallback : value);\n    const __wbJoin = (value, separator) => Array.isArray(value) ? value.join(separator) : (value ?? '');\n    const __wbGet = (value, path) => {\n      if (!path) return value;\n      return String(path).split('.').reduce((acc, part) => (acc == null ? undefined : acc[part]), value);\n    };\n    const __wbBaseUrl = () => (__wbContextUrl || globalThis.location?.href || 'https://example.com/');\n    const __wbResolve = (target, base) => {\n      if (target == null) return '';\n      const text = String(target);\n      try {\n        return /^https?:/i.test(text) ? text : new URL(text, base || __wbBaseUrl()).toString();\n      } catch (_error) {\n        return text;\n      }\n    };\n    const __wbFetch = async (target, options) => {\n      const url = __wbResolve(target, __wbBaseUrl());\n      const resp = await globalThis.fetch(url, Object.assign({ credentials: 'include' }, options || {}));\n      if (!resp.ok) {\n        throw new Error(`HTTP ${resp.status} for ${url}`);\n      }\n      const text = await resp.text();\n      try {\n        return { url, text, data: JSON.parse(text) };\n      } catch (_error) {\n        return { url, text, data: text };\n      }\n    };\n  return (async () => {\n    {\n      const __wbResp = await __wbFetch(\"https://linux.do\");\n      __wbContextUrl = __wbResp.url;\n      __wbContextDocument = new DOMParser().parseFromString(__wbResp.text, 'text/html');\n      data = __wbContextDocument;\n    }\n    {\n      const document = __wbContextDocument || globalThis.document;\n      const location = new URL(__wbBaseUrl());\n      const window = { document, location };\n      const fetch = (target, options) => globalThis.fetch(__wbResolve(target, __wbBaseUrl()), Object.assign({ credentials: 'include' }, options || {}));\n      data = await (((async () => {\n  const mainOnly = (args.main_only);\n  const toLocalTime = (utcStr) => {\n    if (!utcStr) return '';\n    const date = new Date(utcStr);\n    return Number.isNaN(date.getTime()) ? utcStr : date.toLocaleString();\n  };\n  const res = await fetch('/t/(args.id).json', { credentials: 'include' });\n  if (!res.ok) throw new Error('HTTP ' + res.status + ' - 请先登录 linux.do');\n  let data;\n  try { data = await res.json(); } catch { throw new Error('响应不是有效 JSON - 请先登录 linux.do'); }\n  const strip = (html) => (html || '')\n    .replace(/<br\\s*\\/?>/gi, ' ')\n    .replace(/<\\/(p|div|li|blockquote|h[1-6])>/gi, ' ')\n    .replace(/<[^>]+>/g, '')\n    .replace(/&nbsp;/g, ' ')\n    .replace(/&amp;/g, '&')\n    .replace(/&lt;/g, '<')\n    .replace(/&gt;/g, '>')\n    .replace(/&quot;/g, '\"')\n    .replace(/&#(?:(\\d+)|x([0-9a-fA-F]+));/g, (_, dec, hex) => {\n      try { return String.fromCodePoint(dec !== undefined ? Number(dec) : parseInt(hex, 16)); } catch { return ''; }\n    })\n    .replace(/\\s+/g, ' ')\n    .trim();\n  const posts = data?.post_stream?.posts || [];\n  if (mainOnly) {\n    const mainPost = posts.find(p => p.post_number === 1);\n    if (!mainPost) return [];\n    return [{\n      author: mainPost.username || '',\n      content: mainPost.cooked || '',\n      likes: mainPost.like_count || 0,\n      created_at: toLocalTime(mainPost.created_at),\n    }];\n  }\n  return posts.slice(0, (args.limit)).map(p => ({\n    author: p.username,\n    content: strip(p.cooked).slice(0, 200),\n    likes: p.like_count,\n    created_at: toLocalTime(p.created_at),\n  }));\n})())());\n    }\n    {\n      const source = Array.isArray(data) ? data : (data == null ? [] : [data]);\n      data = source.map((item, index) => ({\n        \"author\": item.author,\n        \"content\": item.content,\n        \"likes\": item.likes,\n        \"created_at\": item.created_at,\n      }));\n    }\n    return data;\n  })();\n}"},{"name":"linux_do_user_posts","description":"linux.do 用户的帖子","inputSchema":{"type":"object","properties":{"username":{"type":"string","description":"Username"},"limit":{"type":"number","description":"Number of posts","default":20}},"required":["username"]},"handler":"(params) => {\n  const args = Object.assign({\"limit\": 20}, params || {});\n  let data = null;\n  let __wbContextUrl = globalThis.location?.href || '';\n  let __wbContextDocument = globalThis.document || null;\n    const __wbDefault = (value, fallback) => (value === undefined || value === null || value === '' ? fallback : value);\n    const __wbJoin = (value, separator) => Array.isArray(value) ? value.join(separator) : (value ?? '');\n    const __wbGet = (value, path) => {\n      if (!path) return value;\n      return String(path).split('.').reduce((acc, part) => (acc == null ? undefined : acc[part]), value);\n    };\n    const __wbBaseUrl = () => (__wbContextUrl || globalThis.location?.href || 'https://example.com/');\n    const __wbResolve = (target, base) => {\n      if (target == null) return '';\n      const text = String(target);\n      try {\n        return /^https?:/i.test(text) ? text : new URL(text, base || __wbBaseUrl()).toString();\n      } catch (_error) {\n        return text;\n      }\n    };\n    const __wbFetch = async (target, options) => {\n      const url = __wbResolve(target, __wbBaseUrl());\n      const resp = await globalThis.fetch(url, Object.assign({ credentials: 'include' }, options || {}));\n      if (!resp.ok) {\n        throw new Error(`HTTP ${resp.status} for ${url}`);\n      }\n      const text = await resp.text();\n      try {\n        return { url, text, data: JSON.parse(text) };\n      } catch (_error) {\n        return { url, text, data: text };\n      }\n    };\n  return (async () => {\n    {\n      const __wbResp = await __wbFetch(\"https://linux.do\");\n      __wbContextUrl = __wbResp.url;\n      __wbContextDocument = new DOMParser().parseFromString(__wbResp.text, 'text/html');\n      data = __wbContextDocument;\n    }\n    {\n      const document = __wbContextDocument || globalThis.document;\n      const location = new URL(__wbBaseUrl());\n      const window = { document, location };\n      const fetch = (target, options) => globalThis.fetch(__wbResolve(target, __wbBaseUrl()), Object.assign({ credentials: 'include' }, options || {}));\n      data = await (((async () => {\n  const username = ((args.username));\n  const toLocalTime = (utcStr) => {\n    if (!utcStr) return '';\n    const date = new Date(utcStr);\n    return Number.isNaN(date.getTime()) ? utcStr : date.toLocaleString();\n  };\n  const strip = (html) => (html || '')\n    .replace(/<br\\s*\\/?>/gi, ' ')\n    .replace(/<\\/(p|div|li|blockquote|h[1-6])>/gi, ' ')\n    .replace(/<[^>]+>/g, '')\n    .replace(/&nbsp;/g, ' ')\n    .replace(/&amp;/g, '&')\n    .replace(/&lt;/g, '<')\n    .replace(/&gt;/g, '>')\n    .replace(/&quot;/g, '\"')\n    .replace(/&#(?:(\\d+)|x([0-9a-fA-F]+));/g, (_, dec, hex) => {\n      try { return String.fromCodePoint(dec !== undefined ? Number(dec) : parseInt(hex, 16)); } catch { return ''; }\n    })\n    .replace(/\\s+/g, ' ')\n    .trim();\n  const limit = (__wbDefault((args.limit), 20));\n  const res = await fetch('/user_actions.json?username=' + encodeURIComponent(username) + '&filter=5&offset=0&limit=' + limit, { credentials: 'include' });\n  if (!res.ok) throw new Error('HTTP ' + res.status + ' - 请先登录 linux.do');\n  let data;\n  try { data = await res.json(); } catch { throw new Error('响应不是有效 JSON - 请先登录 linux.do'); }\n  const actions = data?.user_actions || [];\n  return actions.slice(0, limit).map(a => ({\n    author: a.acting_username || a.username || '',\n    title: a.title || '',\n    content: strip(a.excerpt).slice(0, 200),\n    created_at: toLocalTime(a.created_at),\n    url: 'https://linux.do/t/topic/' + a.topic_id + '/' + a.post_number,\n  }));\n})())());\n    }\n    {\n      const source = Array.isArray(data) ? data : (data == null ? [] : [data]);\n      data = source.map((item, index) => ({\n        \"index\": index + 1,\n        \"topic_user\": item.author,\n        \"topic\": item.title,\n        \"reply\": item.content,\n        \"time\": item.created_at,\n        \"url\": item.url,\n      }));\n    }\n    return data;\n  })();\n}"},{"name":"linux_do_user_topics","description":"linux.do 用户创建的话题","inputSchema":{"type":"object","properties":{"username":{"type":"string","description":"Username"},"limit":{"type":"number","description":"Number of topics","default":20}},"required":["username"]},"handler":"(params) => {\n  const args = Object.assign({\"limit\": 20}, params || {});\n  let data = null;\n  let __wbContextUrl = globalThis.location?.href || '';\n  let __wbContextDocument = globalThis.document || null;\n    const __wbDefault = (value, fallback) => (value === undefined || value === null || value === '' ? fallback : value);\n    const __wbJoin = (value, separator) => Array.isArray(value) ? value.join(separator) : (value ?? '');\n    const __wbGet = (value, path) => {\n      if (!path) return value;\n      return String(path).split('.').reduce((acc, part) => (acc == null ? undefined : acc[part]), value);\n    };\n    const __wbBaseUrl = () => (__wbContextUrl || globalThis.location?.href || 'https://example.com/');\n    const __wbResolve = (target, base) => {\n      if (target == null) return '';\n      const text = String(target);\n      try {\n        return /^https?:/i.test(text) ? text : new URL(text, base || __wbBaseUrl()).toString();\n      } catch (_error) {\n        return text;\n      }\n    };\n    const __wbFetch = async (target, options) => {\n      const url = __wbResolve(target, __wbBaseUrl());\n      const resp = await globalThis.fetch(url, Object.assign({ credentials: 'include' }, options || {}));\n      if (!resp.ok) {\n        throw new Error(`HTTP ${resp.status} for ${url}`);\n      }\n      const text = await resp.text();\n      try {\n        return { url, text, data: JSON.parse(text) };\n      } catch (_error) {\n        return { url, text, data: text };\n      }\n    };\n  return (async () => {\n    {\n      const __wbResp = await __wbFetch(\"https://linux.do\");\n      __wbContextUrl = __wbResp.url;\n      __wbContextDocument = new DOMParser().parseFromString(__wbResp.text, 'text/html');\n      data = __wbContextDocument;\n    }\n    {\n      const document = __wbContextDocument || globalThis.document;\n      const location = new URL(__wbBaseUrl());\n      const window = { document, location };\n      const fetch = (target, options) => globalThis.fetch(__wbResolve(target, __wbBaseUrl()), Object.assign({ credentials: 'include' }, options || {}));\n      data = await (((async () => {\n  const username = ((args.username));\n  const toLocalTime = (utcStr) => {\n    if (!utcStr) return '';\n    const date = new Date(utcStr);\n    return Number.isNaN(date.getTime()) ? utcStr : date.toLocaleString();\n  };\n  const res = await fetch('/topics/created-by/' + encodeURIComponent(username) + '.json', { credentials: 'include' });\n  if (!res.ok) throw new Error('HTTP ' + res.status + ' - 请先登录 linux.do');\n  let data;\n  try { data = await res.json(); } catch { throw new Error('响应不是有效 JSON - 请先登录 linux.do'); }\n  const topics = data?.topic_list?.topics || [];\n  return topics.slice(0, (args.limit)).map(t => ({\n    title: t.fancy_title || t.title || '',\n    replies: t.posts_count || 0,\n    created_at: toLocalTime(t.created_at),\n    likes: t.like_count || 0,\n    views: t.views || 0,\n    url: 'https://linux.do/t/topic/' + t.id,\n  }));\n})())());\n    }\n    {\n      const source = Array.isArray(data) ? data : (data == null ? [] : [data]);\n      data = source.map((item, index) => ({\n        \"rank\": index + 1,\n        \"title\": item.title,\n        \"replies\": item.replies,\n        \"created_at\": item.created_at,\n        \"likes\": item.likes,\n        \"views\": item.views,\n        \"url\": item.url,\n      }));\n    }\n    return data;\n  })();\n}"}]}