{
"bookSourceComment": "by·holzora\n详见:https:\/\/books.fishhawk.top\/forum\/66a8e77866bff10c16e51d6a",
"bookSourceGroup": "轻小说",
"bookSourceName": "轻小说机翻机器人",
"bookSourceType": 0,
"bookSourceUrl": "https:\/\/books.fishhawk.top",
"bookUrlPattern": "(?:https?:\/\/)?books\\d?.fishhawk.top\/(?:api\/)?novel\/\\w+\/[\\w-]+",
"customOrder": 0,
"enabled": true,
"enabledCookieJar": false,
"enabledExplore": true,
"exploreUrl": "<js>\ndiscover = [];\npush = (title, url, style) => discover.push({ title: title, url: url, style: { layout_flexGrow: 1, layout_flexBasisPercent: style } });\naddSlots = (count, style) => {\n remainder = count % 3;\n slotsAdd = remainder === 0 ? 0 : 3 - remainder;\n for (i = 0; i < slotsAdd; i++) {\n push(\"\", null, style);\n }\n};\npush(\"收藏夹\", null, 1);\ntry {\n favored = java.ajax(`${baseUrl}\/api\/user\/favored`);\n if (favored.includes(\"Token不合法或者过期\")) {\n push(\"账号未登录\", null, 0.25);\n addSlots(1, 0.25);\n } else {\n favoredData = JSON.parse(favored).favoredWeb;\n favoredData.forEach(item => {\n push(item.title, `\/api\/user\/favored-web\/${item.id}?page={{(page-1)}}&pageSize=30&sort=update`, 0.25);\n });\n addSlots(favoredData.length, 0.25);\n }\n} catch (e) {\n push(\"未知错误\", null, 0.25);\n addSlots(1, 0.25);\n};\npush(\"来源\", null, 1);\nproviders = [\"全部\", \"kakuyomu\", \"syosetu\", \"novelup\", \"hameln\", \"pixiv\", \"alphapolis\"];\nproviders.forEach(provider => {\n url = provider === \"全部\" ? \n `\/api\/novel?page={{(page-1)}}&pageSize=20&query=&provider=${providers.slice(1).join('%2C')}&type=0&level=0&translate=0&sort=0` : \n `\/api\/novel?page={{(page-1)}}&pageSize=20&query=&provider=${provider}&type=0&level=0&translate=0&sort=0`;\n push(provider, url, 0.25);\n});\naddSlots(providers.length, 0.25);\npush(\"类型\", null, 1);\ntypes = [\"全部\", \"连载中\", \"已完结\", \"短篇\"];\ntypes.forEach((type, index) => {\n url = `\/api\/novel?page={{(page-1)}}&pageSize=20&query=&provider=kakuyomu%2Csyosetu%2Cnovelup%2Chameln%2Cpixiv%2Calphapolis&type=${index}&level=0&translate=0&sort=0`;\n push(type, url, 0.25);\n});\naddSlots(types.length, 0.25);\npush(\"排序\", null, 1);\nsorts = [\"更新\", \"点击\"];\nsorts.forEach((sort, index) => {\n url = `\/api\/novel?page={{(page-1)}}&pageSize=20&query=&provider=kakuyomu%2Csyosetu%2Cnovelup%2Chameln%2Cpixiv%2Calphapolis&type=0&level=0&translate=0&sort=${index}`;\n push(sort, url, 0.25);\n});\naddSlots(sorts.length, 0.25);\nJSON.stringify(discover);\n<\/js>",
"header": "<js>\nheader = {\n \"Referer\": baseUrl\n};\nJSON.stringify(header);\n<\/js>",
"jsLib": "",
"lastUpdateTime": "1744514839967",
"loginUi": "[\n {\n \"name\": \"账号\",\n \"type\": \"text\"\n },\n {\n \"name\": \"密码\",\n \"type\": \"password\"\n },\n {\n \"name\": \"注册\",\n \"type\": \"button\",\n \"action\": \"https:\/\/books.fishhawk.top\/sign-in\"\n }\n]",
"loginUrl": "<js>\nfunction login() {\n username = source.getLoginInfoMap().get(\"账号\");\n password = source.getLoginInfoMap().get(\"密码\");\n url = `${baseUrl}\/api\/auth\/sign-in`;\n body = JSON.stringify({\n emailOrUsername: username,\n password: password\n });\n headers = {\n \"Content-Type\": \"application\/json\"\n };\n response = java.post(url, body, headers);\n responseBody = response.body();\n authorizationObject = {\n Authorization: `Bearer ${responseBody}`\n };\n header = JSON.stringify(authorizationObject);\n source.putLoginHeader(header);\n}\nlogin();\n<\/js>",
"respondTime": 180000,
"ruleBookInfo": {
"author": "authors..name",
"coverUrl": "",
"init": "<js>\ninitUrl = book.bookUrl;\nurl = initUrl.includes('api\/novel') ? initUrl : initUrl.replace('novel', 'api\/novel');\nbody = initUrl.includes('api\/novel') ? result : java.ajax(url);\nif (body.includes('ID不合适,应当使用')) {\n match = body.match(\/\\\/\\w+\\\/[\\w-]+\/);\n url = `${book.origin}\/api\/novel${match}`;\n body = java.ajax(url);\n}\nif (body.includes(\"Token不合法或者过期\")) {\n java.toast(\"当前会话已失效,请重新登录\");\n}\njava.put('url', url);\nbody\n<\/js>",
"intro": "<js>\nvalue = String(book.getVariable(\"custom\")) || String(source.getVariable());\nresult = JSON.parse(result);\nintro = ['5', '8', '9'].includes(value) ? result.introductionJp : result.introductionZh || result.introductionJp;\ntimeStamp = '{{$.toc[-1].createAt}}' || '{{$.syncAt}}';\nintro = ' 📬最近' + ('{{$.toc[-1].createAt}}' ? '更新' : '同步') + ':' + java.timeFormat(timeStamp * 1000) + '\\n‎\\n' + intro;\nintro\n<\/js>",
"kind": "{{$.type##中|已}},{{$.attentions}},{{$.keywords[:3]}}",
"lastChapter": "",
"name": "<js>\nvalue = String(book.getVariable(\"custom\")) || String(source.getVariable());\nresult = JSON.parse(result);\nname = ['5', '8', '9'].includes(value) ? result.titleJp : result.titleZh || result.titleJp;\nname\n<\/js>",
"tocUrl": "{{java.get('url')}}",
"wordCount": "totalCharacters"
},
"ruleContent": {
"content": "<js>\nvalue = String(book.getVariable(\"custom\")) || String(source.getVariable());\nget = (value >= '1' && value <= '9') ? value : '1';\nconfig = {\n 1: 'sakuraParagraphs',\n 2: 'gptParagraphs',\n 3: 'youdaoParagraphs',\n 4: 'baiduParagraphs',\n 5: 'paragraphs'\n};\nresult = JSON.parse(result);\ncontent = result[config[get]] || result[config[2]] || result[config[3]] || result[config[4]] || result[config[5]];\nif (['6', '7', '8', '9'].includes(get)) {\n type = ['6', '8'].includes(get);\n jpContent = result[config[5]];\n cnContent = result[config[1]] || result[config[2]] || result[config[3]] || result[config[4]];\n if (cnContent) {\n content = jpContent.flatMap((jp, i) => (type ? [cnContent[i], jp] : [jp, cnContent[i]])).filter(Boolean);\n } else {\n content = jpContent;\n }\n};\nregex = content => {\n displayedImages = new Set();\n return JSON.stringify(content).replace(\/<图片>(https?:\\\/\\\/[^\"\\s]+)\/g, (_, p1) => {\n if (displayedImages.has(p1)) return '';\n displayedImages.add(p1);\n return '<img src=\"' + p1 + ',' + JSON.stringify({headers:{Referer:p1.match(\/https?:\\\/\\\/[^\\\/]+\/)[0].replace(\/i.pximg.net\/,'www.pixiv.net')}}) + '\">';\n });\n};\nformat = content => content.replace(\/^\\[|\\]$\/g, '').split(',').map(item => item.trim().replace(\/^\"|\"$\/g, '')).filter(Boolean).map((item, i) => item.startsWith('{\"headers\"') && i > 0 ? ',' + item : ' ' + item).join(\"\\n\");\nformat(regex(content));\n<\/js>",
"payAction": "",
"replaceRegex": "",
"title": "",
"webJs": ""
},
"ruleExplore": {
"bookList": "",
"intro": ""
},
"ruleSearch": {
"author": "",
"bookList": "<js>\nif (result.includes(\"Token不合法或者过期\")) {\n java.toast(\"当前会话已失效,请重新登录\");\n}\nresult\n<\/js>\nitems",
"bookUrl": "\/api\/novel\/{{$.providerId}}\/{{$..novelId}}",
"checkKeyWord": "",
"intro": " 源站:{{$.providerId}}{{'\\n'}}📬最近同步:{{java.timeFormat(java.getString('$.updateAt')*1000)}}{{'\\n'}}总计{{$.total}}|百度{{$.baidu}}|有道{{$.youdao}}|gpt{{$.gpt}}|sakura{{$.sakura}}",
"kind": "{{$.type##中|已}},{{$.attentions}},{{$.keywords}}",
"lastChapter": "",
"name": "<js>\nvalue = String(book.getVariable(\"custom\")) || String(source.getVariable());\nname = ['5', '8', '9'].includes(value) ? result.titleJp : result.titleZh || result.titleJp;\nname\n<\/js>"
},
"ruleToc": {
"chapterList": "toc\n<js>\nresult.forEach(item => {\n if ('chapterId' in item) {\n item.isVolume = false;\n item.url = `${baseUrl}\/chapter\/${item.chapterId}`;\n timeStamp = item.createAt ? item.createAt * 1000 : null;\n if (timeStamp) {\n item.time = java.timeFormat(timeStamp);\n } else {\n item.time = '';\n }\n } else {\n item.isVolume = true;\n }\n});\nresult\n<\/js>",
"chapterName": "<js>\nvalue = String(book.getVariable(\"custom\")) || String(source.getVariable());\nname = ['5', '8', '9'].includes(value) ? result.titleJp : result.titleZh || result.titleJp;\nname\n<\/js>",
"chapterUrl": "url",
"formatJs": "",
"isPay": "",
"isVolume": "isVolume",
"preUpdateJs": "",
"updateTime": "time"
},
"searchUrl": "<js>\nproviders = {\n kakuyomu: \/(?:https?:\\\/\\\/)?kakuyomu\\.jp\\\/works\\\/(\\d+)\/,\n syosetu: \/(?:https?:\\\/\\\/)?(ncode|novel18)\\.syosetu\\.com\\\/(\\w+)\/,\n novelup: \/(?:https?:\\\/\\\/)?novelup\\.plus\\\/story\\\/(\\d+)\/,\n hameln: \/(?:https?:\\\/\\\/)?syosetu\\.org\\\/novel\\\/(\\d+)\/,\n pixiv: \/(?:https?:\\\/\\\/)?(www\\.pixiv\\.net\\\/novel\\\/series\\\/(\\d+)|www\\.pixiv\\.net\\\/novel\\\/show\\.php\\?id=(\\d+))\/,\n alphapolis: \/(?:https?:\\\/\\\/)?www\\.alphapolis\\.co\\.jp\\\/novel\\\/(\\d+)\\\/(\\d+)\/,\n sakura: \/(?:https?:\\\/\\\/)?books\\d?\\.fishhawk\\.top\\\/(?:api\\\/)?novel\\\/(\\w+)\\\/([\\w-]+)\/\n};\nfor (provider in providers) {\n match = key.match(providers[provider]);\n if (match) {\n providerId = provider === 'sakura' ? match[1] : provider;\n novelId = provider === 'alphapolis' ? `${match[1]}-${match[2]}` : provider === 'pixiv' ? (match[3] ? `s${match[3]}` : match[2]) : (match[2] || match[1]);\n url = `${baseUrl}\/api\/novel\/${providerId}\/${novelId}`;\n break;\n } else {\n url = `${baseUrl}\/api\/novel?page=${page-1}&pageSize=20&query=${key}&provider=kakuyomu,syosetu,novelup,hameln,pixiv,alphapolis&type=0&level=0&translate=0&sort=1`;\n }\n url\n}\n<\/js>",
"variableComment": "输入对应数字刷新即可获取对应文本,{1: \"sakura译文\", 2: \"gpt译文\", 3: \"有道译文\", 4: \"百度译文\", 5: \"原文\", 6: \"中日\", 7: \"日中\", 8: \"5+6\", 9: \"5+7\"},默认sakura译文。书籍变量作用于一本书,源变量作用于整个书源,书籍变量优先级高于源变量。",
"weight": 0
}