[{"bookSourceUrl":"https:\/\/www.hen0.com","bookSourceName":"笔下文学 hen0.com","enabledExplore":false,"enabled":true,"bookSourceGroup":"youchen","author":"youchen","help":false,"html":"<!DOCTYPE html>\n<html lang=\"zh-CN\">\n<head>\n  <meta charset=\"UTF-8\">\n  <title>笔下文学 hen0.com 原生书源<\/title>\n<\/head>\n<body><\/body>\n<script src=\"https:\/\/vc.jd.com\/web\/js\/jquery-3.1.1.min.js\"><\/script>\n<script>\n  var isCookieJar = true;\n  class FlutterJSBridge {\n    constructor() { this.init(); }\n    init() {\n      if (window.flutter_inappwebview) { this.isReady = true; this.CookieJar(); }\n      else window.addEventListener('flutterInAppWebViewPlatformReady', () => { this.isReady = true; this.CookieJar(); });\n    }\n    async CookieJar() { try { return await window.flutter_inappwebview.callHandler('CookieJar', isCookieJar); } catch (e) { return false; } }\n    async text(type, str) { try { return await window.flutter_inappwebview.callHandler('text', type, str); } catch (e) { return false; } }\n    async utf8ToGbkUrlEncoded(str) { try { return await window.flutter_inappwebview.callHandler('utf8ToGbkUrlEncoded', str); } catch (e) { return encodeURIComponent(str); } }\n  }\n  class Http {\n    async Get(url, headers, followRedirects) {\n      try { return await window.flutter_inappwebview.callHandler('http', 'get', url, '', JSON.stringify(headers || {}), followRedirects, ''); } catch (e) { return null; }\n    }\n    async Post(url, headers, body, contenttype, followRedirects) {\n      try { return await window.flutter_inappwebview.callHandler('http', 'post', url, body, JSON.stringify(headers || {}), followRedirects, contenttype); } catch (e) { return null; }\n    }\n  }\n  const flutterBridge = new FlutterJSBridge();\n  const http = new Http();\n  const baseurl = 'https:\/\/www.hen0.com';\n  const headers = {\n    'User-Agent': 'Mozilla\/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit\/537.36 Chrome\/131.0 Safari\/537.36',\n    'Referer': baseurl + '\/'\n  };\n\n  function abs(url, base) {\n    if (!url) return '';\n    try { return new URL(url, base || baseurl + '\/').href; } catch (e) { return url || ''; }\n  }\n  function cleanText(value) { return String(value || '').replace(\/\\s+\/g, ' ').trim(); }\n  function responseHtml(result) { return result && typeof result.data === 'string' ? result.data : ''; }\n  function parse(html) { return $($.parseHTML(html, document, true)); }\n  function bookFromDetail(root, bookurl) {\n    var meta = root.find('.book-meta .label');\n    var cover = root.find('.book-cover-large').attr('src');\n    var latest = root.find('.book-meta').find('a').last().text();\n    return {\n      bookUrl: bookurl, name: cleanText(root.find('.book-title').first().text()),\n      author: cleanText(root.find('.book-author').first().text()).replace(\/^作者[：:]\\s*\/, ''),\n      kind: cleanText(meta.eq(0).text()).replace(\/^分类[：:]\\s*\/, ''), coverUrl: abs(cover),\n      intro: '@html:' + (root.find('.book-tags').first().html() || ''), tocUrl: bookurl,\n      wordCount: cleanText(meta.eq(2).text()).replace(\/^字数[：:]\\s*\/, ''), type: 0,\n      latestChapterTitle: cleanText(latest)\n    };\n  }\n\n  async function search(key, page) {\n    if (page > 1) return '[]';\n    var body = 'searchkey=' + (await flutterBridge.utf8ToGbkUrlEncoded(key || ''));\n    var result = await http.Post(baseurl + '\/search.html', headers, body, 'application\/x-www-form-urlencoded', true);\n    var html = responseHtml(result);\n    await flutterBridge.text(0, html);\n    var root = parse(html), books = [];\n    \/\/ Exact matches are redirected by the site directly to a detail page.\n    if (root.find('.book-title').length) {\n      var firstChapter = abs(root.find('.action-buttons a.btn-primary').first().attr('href'));\n      var detailUrl = firstChapter.replace(\/[^\/]+\\.html(?:[?#].*)?$\/, '');\n      if (detailUrl) books.push(bookFromDetail(root, detailUrl));\n      return JSON.stringify(books);\n    }\n    root.find('table tbody tr').each(function(index) {\n      var row = $(this), link = row.find('td').eq(0).find('a').first();\n      var href = link.attr('href');\n      if (!href) return;\n      var cells = row.find('td');\n      books.push({\n        bookUrl: abs(href), name: cleanText(link.text()), author: cleanText(cells.eq(1).text()),\n        kind: '', coverUrl: '', intro: '', tocUrl: abs(href), wordCount: '', type: 0,\n        latestChapterTitle: cleanText(cells.eq(2).text())\n      });\n    });\n    return JSON.stringify(books);\n  }\n\n  async function info(bookurl) {\n    var result = await http.Get(bookurl, headers, true), html = responseHtml(result);\n    await flutterBridge.text(1, html);\n    return JSON.stringify(bookFromDetail(parse(html), bookurl));\n  }\n\n  function chapterNameMap(html) {\n    var names = {};\n    var re = \/\\.p_(\\d+)::after\\s*\\{\\s*content\\s*:\\s*(['\"])([\\s\\S]*?)\\2\\s*\\}\/g, match;\n    while ((match = re.exec(html || ''))) {\n      names[match[1]] = match[3].replace(\/\\\\(['\"])\/g, '$1').replace(\/\\\\n\/g, '\\n').trim();\n    }\n    return names;\n  }\n\n  function appendChapters(html, pageUrl, chapters, seen) {\n    var names = chapterNameMap(html), anchorRe = \/<a\\b([^>]*)>([\\s\\S]*?)<\\\/a>\/gi, match;\n    \/\/ Parse raw anchors because the visible title is not text content.\n    while ((match = anchorRe.exec(html || ''))) {\n      var attrs = match[1], inner = match[2];\n      var hrefMatch = attrs.match(\/\\bhref\\s*=\\s*(['\"])(.*?)\\1\/i);\n      var classMatch = attrs.match(\/\\bclass\\s*=\\s*(['\"])(.*?)\\1\/i);\n      var href = hrefMatch ? hrefMatch[2] : '', className = classMatch ? classMatch[2] : '';\n      var idMatch = className.match(\/(?:^|\\s)p_(\\d+)(?:\\s|$)\/);\n      if (!idMatch || !href) continue;\n      var idNumber = idMatch[1];\n      var visibleText = cleanText(inner.replace(\/<[^>]*>\/g, ' '));\n      var name = visibleText || cleanText(names[idNumber]);\n      if (!name) continue;\n      var id = abs(href, pageUrl);\n      if (seen[id]) continue;\n      seen[id] = true;\n      chapters.push({ name: name, chapterId: id, index: chapters.length, isPay: false, isVip: false, isVolume: false, tag: '' });\n    }\n  }\n\n  async function chapter(tocUrl) {\n    \/\/ Requests must stay sequential: some Windows builds cannot settle concurrent\n    \/\/ Http bridge calls. The site exposes every catalog page in <option value>.\n    var result = await http.Get(tocUrl, headers, true), html = responseHtml(result);\n    if (!html) return '[]';\n    await flutterBridge.text(2, html);\n    var chapters = [], seenChapters = {}, pageUrls = [abs(tocUrl)], optionRe = \/<option\\b[^>]*value\\s*=\\s*(['\"])(.*?)\\1\/gi, optionMatch;\n    while ((optionMatch = optionRe.exec(html))) {\n      var optionUrl = abs(optionMatch[2], tocUrl);\n      if (optionUrl.replace(\/_1\\\/$\/, '\/') === pageUrls[0]) continue;\n      if (optionUrl && pageUrls.indexOf(optionUrl) < 0) pageUrls.push(optionUrl);\n    }\n    appendChapters(html, tocUrl, chapters, seenChapters);\n    for (var pageIndex = 1; pageIndex < pageUrls.length; pageIndex++) {\n      var pageResult = await http.Get(pageUrls[pageIndex], headers, true);\n      var pageHtml = responseHtml(pageResult);\n      if (pageHtml) appendChapters(pageHtml, pageUrls[pageIndex], chapters, seenChapters);\n    }\n    return JSON.stringify(chapters);\n  }\n\n  function cssAfterText(root) {\n    var style = root.find('#chapter-content style').text() || '';\n    var pieces = [], re = \/#chapter-content\\s+\\.([^:]+)::after\\s*\\{\\s*content\\s*:\\s*(['\"])([\\s\\S]*?)\\2\\s*\\}\/g, match;\n    while ((match = re.exec(style))) pieces.push(match[3].replace(\/\\\\(['\"])\/g, '$1').replace(\/\\\\n\/g, '\\n'));\n    if (!pieces.length) {\n      root.find('#chapter-content').contents().each(function() {\n        if (this.nodeType === 3) pieces.push(cleanText(this.nodeValue));\n      });\n    }\n    return pieces.filter(function(x) { return x !== ''; }).join('\\n').trim();\n  }\n\n  async function content(url) {\n    var current = abs(url), first = true, parts = [], guard = 0, baseChapter = current.replace(\/_\\d+(?=\\.html$)\/, '');\n    while (current && guard++ < 20) {\n      var result = await http.Get(current, headers, true), html = responseHtml(result);\n      if (!html) break;\n      var root = parse(html);\n      if (first) await flutterBridge.text(3, html);\n      var text = cssAfterText(root);\n      if (text) parts.push(text);\n      var next = root.find('.chapter-nav a').filter(function() { return \/下一页\/.test($(this).text()); }).first().attr('href');\n      if (!next) break;\n      var nextUrl = abs(next, current);\n      if (nextUrl === current || nextUrl.replace(\/_\\d+(?=\\.html$)\/, '') !== baseChapter) break;\n      current = nextUrl; first = false;\n    }\n    return parts.join('\\n').trim();\n  }\n\n  async function getfinds() { return '[]'; }\n  async function find(url, page) { return '[]'; }\n  async function pay(bookurl, url) {}\n  async function getloginurl() { return ''; }\n  async function login() {}\n  async function imagedecrypt(url, image) {}\n  async function shouldOverrideUrlLoading(url) { return true; }\n<\/script>\n<\/html>\n","login":false,"customButton":false,"eventListener":false,"lastUpdateTime":"1787299157566"}]