{"url_pattern":"^https?://([a-z0-9-]+\\.)?reddit\\.com(/.*)?$","site_name":"reddit","allowed_domains":["reddit.com"],"tools":[{"name":"reddit_context","description":"获取评论的 ancestor chain（从根帖到目标评论的完整路径）","inputSchema":{"type":"object","properties":{"url":{"type":"string","description":"Reddit comment URL (utm params are stripped automatically)"}},"required":["url"]},"handler":"(params) => {\n  const run = async function(args) {\n\n      if (!args.url) return {error: 'Missing argument: url', hint: 'Provide a Reddit comment URL'};\n      const path = args.url.replace(/https?:\\/\\/[^/]*/, '').replace(/\\?.*/, '').replace(/\\/*$/, '/');\n\n      // Extract comment_id from various URL formats:\n      //   /r/sub/comments/POST_ID/comment/COMMENT_ID/    (new shreddit style)\n      //   /r/sub/comments/POST_ID/slug/COMMENT_ID/       (old style)\n      let comment_id = (path.match(/\\/comment\\/([^/]+)/) || [])[1];\n      if (!comment_id) {\n        const parts = path.match(/\\/comments\\/[^/]*\\/[^/]*\\/([^/]*)/);\n        comment_id = parts ? parts[1] : null;\n      }\n      if (!comment_id) return {error: 'Cannot extract comment_id from URL', hint: 'Expected: .../comment/<id>/'};\n\n      // Build API URL: /r/sub/comments/POST_ID/COMMENT_ID/.json (the only format that works since 2025)\n      const postMatch = path.match(/(\\/r\\/[^/]+\\/comments\\/[^/]+\\/)/);\n      if (!postMatch) return {error: 'Cannot extract post path from URL', hint: 'Expected: /r/sub/comments/POST_ID/...'};\n      const apiPath = postMatch[1] + comment_id + '/';\n\n      const resp = await fetch(apiPath + '.json?context=10&raw_json=1', {credentials: 'include'});\n      if (!resp.ok) return {error: 'HTTP ' + resp.status};\n      const d = await resp.json();\n      if (!d[0]?.data?.children?.[0]?.data) return {error: 'Unexpected response', hint: 'Post may be deleted or URL is incorrect'};\n      const post = d[0].data.children[0].data;\n\n      function flatten(children, depth) {\n        let result = [];\n        for (const child of children) {\n          if (child.kind !== 't1') continue;\n          const c = child.data;\n          result.push({id: c.name, parent_id: c.parent_id, author: c.author, score: c.score, body: c.body, depth});\n          if (c.replies?.data?.children)\n            result = result.concat(flatten(c.replies.data.children, depth + 1));\n        }\n        return result;\n      }\n\n      const comments = flatten(d[1]?.data?.children || [], 0);\n      const target = comments.find(c => c.id === 't1_' + comment_id);\n      if (!target) return {error: 'Comment t1_' + comment_id + ' not found', hint: 'Comment may be deleted or URL is incorrect'};\n\n      let chain = [], cur = target;\n      while (cur) { chain.unshift(cur); cur = comments.find(c => c.id === cur.parent_id); }\n\n      return {\n        post: {id: post.name, title: post.title, author: post.author, url: 'https://www.reddit.com' + (post.permalink || '')},\n        target_comment: target,\n        ancestor_chain: chain\n      };\n  };\n  return run(params || {});\n}"},{"name":"reddit_hot","description":"获取 subreddit 热门帖子（或 Reddit 首页）","inputSchema":{"type":"object","properties":{"subreddit":{"type":"string","description":"Subreddit name without r/ prefix (omit for front page)"},"count":{"type":"string","description":"Number of posts (default 25, max 100)"},"after":{"type":"string","description":"Fullname of the last item for pagination (e.g. t3_abc123)"}},"required":null},"handler":"(params) => {\n  const run = async function(args) {\n\n      const count = Math.min(parseInt(args.count) || 25, 100);\n      const base = args.subreddit ? '/r/' + args.subreddit : '';\n      let url = base + '/hot.json?limit=' + count + '&raw_json=1';\n      if (args.after) url += '&after=' + args.after;\n\n      const resp = await fetch(url, {credentials: 'include'});\n      if (!resp.ok) return {error: 'HTTP ' + resp.status, hint: resp.status === 404 ? 'Subreddit not found' : 'API error'};\n      const d = await resp.json();\n      if (!d.data?.children) return {error: 'Unexpected response', hint: 'Reddit may be rate-limiting or returning a login page'};\n\n      const posts = d.data.children.map((c, i) => ({\n        rank: i + 1,\n        id: c.data.name,\n        title: c.data.title,\n        author: c.data.author,\n        subreddit: c.data.subreddit_name_prefixed,\n        score: c.data.score,\n        upvote_ratio: c.data.upvote_ratio,\n        num_comments: c.data.num_comments,\n        created_utc: c.data.created_utc,\n        url: c.data.url,\n        permalink: 'https://www.reddit.com' + c.data.permalink,\n        selftext_preview: (c.data.selftext || '').substring(0, 200),\n        is_self: c.data.is_self,\n        link_flair_text: c.data.link_flair_text || null\n      }));\n\n      return {\n        subreddit: args.subreddit || 'front page',\n        count: posts.length,\n        after: d.data.after || null,\n        posts\n      };\n  };\n  return run(params || {});\n}"},{"name":"reddit_search","description":"搜索 Reddit 帖子","inputSchema":{"type":"object","properties":{"query":{"type":"string","description":"Search query"},"subreddit":{"type":"string","description":"Limit search to a subreddit (without r/ prefix)"},"sort":{"type":"string","description":"Sort order: relevance (default), hot, top, new, comments"},"time":{"type":"string","description":"Time filter: all (default), hour, day, week, month, year"},"count":{"type":"string","description":"Number of results (default 25, max 100)"},"after":{"type":"string","description":"Fullname of the last item for pagination"}},"required":["query"]},"handler":"(params) => {\n  const run = async function(args) {\n\n      if (!args.query) return {error: 'Missing argument: query', hint: 'Provide a search query'};\n\n      const count = Math.min(parseInt(args.count) || 25, 100);\n      const sort = args.sort || 'relevance';\n      const time = args.time || 'all';\n\n      let url;\n      if (args.subreddit) {\n        url = '/r/' + args.subreddit + '/search.json?restrict_sr=on&q=' + encodeURIComponent(args.query);\n      } else {\n        url = '/search.json?q=' + encodeURIComponent(args.query);\n      }\n      url += '&sort=' + sort + '&t=' + time + '&limit=' + count + '&raw_json=1';\n      if (args.after) url += '&after=' + args.after;\n\n      const resp = await fetch(url, {credentials: 'include'});\n      if (!resp.ok) return {error: 'HTTP ' + resp.status, hint: resp.status === 404 ? 'Subreddit not found' : 'API error'};\n      const d = await resp.json();\n      if (!d.data?.children) return {error: 'Unexpected response', hint: 'Reddit may be rate-limiting or returning a login page'};\n\n      const posts = d.data.children.map(c => ({\n        id: c.data.name,\n        title: c.data.title,\n        author: c.data.author,\n        subreddit: c.data.subreddit_name_prefixed,\n        score: c.data.score,\n        num_comments: c.data.num_comments,\n        created_utc: c.data.created_utc,\n        url: c.data.url,\n        permalink: 'https://www.reddit.com' + c.data.permalink,\n        selftext_preview: (c.data.selftext || '').substring(0, 200),\n        is_self: c.data.is_self,\n        link_flair_text: c.data.link_flair_text || null\n      }));\n\n      return {\n        query: args.query,\n        subreddit: args.subreddit || null,\n        sort,\n        time,\n        count: posts.length,\n        after: d.data.after || null,\n        posts\n      };\n  };\n  return run(params || {});\n}"},{"name":"reddit_subreddit","description":"Get posts from a specific Subreddit","inputSchema":{"type":"object","properties":{"name":{"type":"string","description":""},"sort":{"type":"string","description":"Sorting method: hot, new, top, rising, controversial","default":"hot"},"time":{"type":"string","description":"Time filter for top/controversial: hour, day, week, month, year, all","default":"all"},"limit":{"type":"number","description":"","default":15}},"required":["name"]},"handler":"(params) => {\n  const args = Object.assign({\"sort\": \"hot\", \"time\": \"all\", \"limit\": 15}, 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 || '');\n    const __wbResolve = (target, base) => {\n      if (target == null) return '';\n      const text = String(target);\n      try {\n        if (/^https?:/i.test(text)) return text;\n        const resolvedBase = base || __wbBaseUrl();\n        return resolvedBase ? new URL(text, resolvedBase).toString() : text;\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://www.reddit.com\");\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  let sub = ((args.name));\n  if (sub.startsWith('r/')) sub = sub.slice(2);\n  const sort = ((args.sort));\n  const time = ((args.time));\n  const limit = (args.limit);\n  let url = '/r/' + sub + '/' + sort + '.json?limit=' + limit + '&raw_json=1';\n  if ((sort === 'top' || sort === 'controversial') && time) {\n    url += '&t=' + time;\n  }\n  const res = await fetch(url, { credentials: 'include' });\n  const j = await res.json();\n  return j?.data?.children || [];\n})())());\n    }\n    {\n      const source = Array.isArray(data) ? data : (data == null ? [] : [data]);\n      data = source.map((item, index) => ({\n        \"title\": item.data.title,\n        \"author\": item.data.author,\n        \"upvotes\": item.data.score,\n        \"comments\": item.data.num_comments,\n        \"url\": `https://www.reddit.com${item.data.permalink}`,\n      }));\n    }\n    if (Array.isArray(data)) data = data.slice(0, Number(args.limit) || 0);\n    return data;\n  })();\n}"},{"name":"reddit_thread","description":"获取 Reddit 帖子的完整讨论树","inputSchema":{"type":"object","properties":{"url":{"type":"string","description":"Reddit post URL"}},"required":["url"]},"handler":"(params) => {\n  const run = async function(args) {\n\n      if (!args.url) return {error: 'Missing argument: url', hint: 'Provide a Reddit post URL'};\n      let path = args.url.replace(/https?:\\/\\/[^/]*/, '').replace(/\\?.*/, '').replace(/\\/*$/, '/');\n      // Normalize to /r/sub/comments/POST_ID/ — strip slug and any comment suffixes\n      // Handles: .../comments/ID/slug/, .../comments/ID/comment/CID/, .../comments/ID/slug/CID/\n      const m = path.match(/(\\/r\\/[^/]+\\/comments\\/[^/]+\\/)/);\n      if (m) path = m[1];\n      const resp = await fetch(path + '.json?limit=500&depth=10&raw_json=1', {credentials: 'include'});\n      if (!resp.ok) return {error: 'HTTP ' + resp.status};\n      const d = await resp.json();\n      if (!d[0]?.data?.children?.[0]?.data) return {error: 'Unexpected response', hint: 'Post may be deleted or URL is incorrect'};\n      const post = d[0].data.children[0].data;\n\n      function flatten(children, depth) {\n        let result = [];\n        for (const child of children) {\n          if (child.kind !== 't1') continue;\n          const c = child.data;\n          result.push({id: c.name, parent_id: c.parent_id, author: c.author, score: c.score, body: c.body, depth});\n          if (c.replies?.data?.children)\n            result = result.concat(flatten(c.replies.data.children, depth + 1));\n        }\n        return result;\n      }\n\n      const comments = flatten(d[1]?.data?.children || [], 0);\n      return {\n        post: {id: post.name, title: post.title, author: post.author, subreddit: post.subreddit_name_prefixed,\n          score: post.score, num_comments: post.num_comments, selftext: post.selftext, url: post.url, created_utc: post.created_utc},\n        comments_total: comments.length,\n        comments\n      };\n  };\n  return run(params || {});\n}"},{"name":"reddit_user","description":"View a Reddit user profile","inputSchema":{"type":"object","properties":{"username":{"type":"string","description":""}},"required":["username"]},"handler":"(params) => {\n  const args = Object.assign({}, 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 || '');\n    const __wbResolve = (target, base) => {\n      if (target == null) return '';\n      const text = String(target);\n      try {\n        if (/^https?:/i.test(text)) return text;\n        const resolvedBase = base || __wbBaseUrl();\n        return resolvedBase ? new URL(text, resolvedBase).toString() : text;\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://www.reddit.com\");\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 name = username.startsWith('u/') ? username.slice(2) : username;\n  const res = await fetch('/user/' + name + '/about.json?raw_json=1', {\n    credentials: 'include'\n  });\n  const d = await res.json();\n  const u = d?.data || d || {};\n  const created = u.created_utc ? new Date(u.created_utc * 1000).toISOString().split('T')[0] : '-';\n  return [\n    { field: 'Username', value: 'u/' + (u.name || name) },\n    { field: 'Post Karma', value: String(u.link_karma || 0) },\n    { field: 'Comment Karma', value: String(u.comment_karma || 0) },\n    { field: 'Total Karma', value: String(u.total_karma || (u.link_karma||0) + (u.comment_karma||0)) },\n    { field: 'Account Created', value: created },\n    { field: 'Gold', value: u.is_gold ? '⭐ Yes' : 'No' },\n    { field: 'Verified', value: u.verified ? '✅ Yes' : 'No' },\n  ];\n})())());\n    }\n    {\n      const source = Array.isArray(data) ? data : (data == null ? [] : [data]);\n      data = source.map((item, index) => ({\n        \"field\": item.field,\n        \"value\": item.value,\n      }));\n    }\n    return data;\n  })();\n}"},{"name":"reddit_user_posts","description":"View a Reddit user's submitted posts","inputSchema":{"type":"object","properties":{"username":{"type":"string","description":""},"limit":{"type":"number","description":"","default":15}},"required":["username"]},"handler":"(params) => {\n  const args = Object.assign({\"limit\": 15}, 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 || '');\n    const __wbResolve = (target, base) => {\n      if (target == null) return '';\n      const text = String(target);\n      try {\n        if (/^https?:/i.test(text)) return text;\n        const resolvedBase = base || __wbBaseUrl();\n        return resolvedBase ? new URL(text, resolvedBase).toString() : text;\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://www.reddit.com\");\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 name = username.startsWith('u/') ? username.slice(2) : username;\n  const limit = (args.limit);\n  const res = await fetch('/user/' + name + '/submitted.json?limit=' + limit + '&raw_json=1', {\n    credentials: 'include'\n  });\n  const d = await res.json();\n  return (d?.data?.children || []).map(c => ({\n    title: c.data.title,\n    subreddit: c.data.subreddit_name_prefixed,\n    score: c.data.score,\n    comments: c.data.num_comments,\n    url: 'https://www.reddit.com' + c.data.permalink,\n  }));\n})())());\n    }\n    {\n      const source = Array.isArray(data) ? data : (data == null ? [] : [data]);\n      data = source.map((item, index) => ({\n        \"title\": item.title,\n        \"subreddit\": item.subreddit,\n        \"score\": item.score,\n        \"comments\": item.comments,\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":"reddit_user_comments","description":"View a Reddit user's comment history","inputSchema":{"type":"object","properties":{"username":{"type":"string","description":""},"limit":{"type":"number","description":"","default":15}},"required":["username"]},"handler":"(params) => {\n  const args = Object.assign({\"limit\": 15}, 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 || '');\n    const __wbResolve = (target, base) => {\n      if (target == null) return '';\n      const text = String(target);\n      try {\n        if (/^https?:/i.test(text)) return text;\n        const resolvedBase = base || __wbBaseUrl();\n        return resolvedBase ? new URL(text, resolvedBase).toString() : text;\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://www.reddit.com\");\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 name = username.startsWith('u/') ? username.slice(2) : username;\n  const limit = (args.limit);\n  const res = await fetch('/user/' + name + '/comments.json?limit=' + limit + '&raw_json=1', {\n    credentials: 'include'\n  });\n  const d = await res.json();\n  return (d?.data?.children || []).map(c => {\n    let body = c.data.body || '';\n    if (body.length > 300) body = body.slice(0, 300) + '...';\n    return {\n      subreddit: c.data.subreddit_name_prefixed,\n      score: c.data.score,\n      body: body,\n      url: 'https://www.reddit.com' + c.data.permalink,\n    };\n  });\n})())());\n    }\n    {\n      const source = Array.isArray(data) ? data : (data == null ? [] : [data]);\n      data = source.map((item, index) => ({\n        \"subreddit\": item.subreddit,\n        \"score\": item.score,\n        \"body\": item.body,\n        \"url\": item.url,\n      }));\n    }\n    if (Array.isArray(data)) data = data.slice(0, Number(args.limit) || 0);\n    return data;\n  })();\n}"}]}