晴天书荒广场
https://www.baidu.com/晴天番茄书荒广场
分享者: 晴天 (8653)发布时间: 2天前
https://api.qingtian618.com
需要配合晴天融合完全版书源一起使用,支持直接跳转到对应书籍
4.7.17
新增七猫、塔读、书旗广场
4.7.16
修复bug
4.7.15
新增番茄书荒广场
{ "articleStyle": 0, "customOrder": -101052, "enableJs": true, "enabled": true, "enabledCookieJar": true, "injectJs": "const host = document.location.host;\nconst path = document.location.pathname;\nconst params = new URLSearchParams(window.location.search);\nconst currentUrl = window.location.href;\nconst url = new URL(currentUrl);\n\n\/\/ 域名重定向\nif (host === 'changdunovel.com' && path.endsWith('share-v2.html')) {\n const bookid = params.get('book_id');\n if (bookid) {\n document.location.href = `https:\/\/fanqienovel.com\/page\/${bookid}`;\n }\n}\n\nconst adBlockSystem = setInterval(() => {\n const shouldSkip = (el) => {\n return el.closest('.horizontal-slip-modal-card') || \n el.closest('.topic-comment-item.new-card-style');\n };\n document.querySelectorAll('.page-reader-btn.download, .download-btn, .app-download-popup, .download-app')\n .forEach(el => {\n if (!shouldSkip(el) && el.textContent.match(\/下载|APP|安装\/)) {\n el.remove();\n }\n });\n document.querySelectorAll('.mask, .popup-layer, .modal-backdrop, .popup-container')\n .forEach(el => {\n if (!shouldSkip(el)) {\n el.remove();\n }\n });\n document.querySelectorAll('.float-ad, .bottom-ad, .fixed-ad')\n .forEach(ad => {\n if (!shouldSkip(ad)) {\n ad.remove();\n }\n });\n}, 500);\n\nconst fixBookClick = setInterval(() => {\n const shouldSkip = (el) => {\n return el.closest('.horizontal-slip-modal-card') || \n el.closest('.topic-comment-item.new-card-style');\n };\n \n document.querySelectorAll('.horizontal-slip-books-item').forEach(item => {\n if (shouldSkip(item)) return;\n \n item.style.pointerEvents = 'auto';\n item.style.cursor = 'pointer';\n \n if (item.id && !item.hasAttribute('data-click-fixed')) {\n item.setAttribute('data-click-fixed', 'true');\n item.addEventListener('click', (e) => {\n e.preventDefault();\n window.location.href = `https:\/\/fanqienovel.com\/page\/${item.id}`;\n });\n }\n });\n \n document.querySelectorAll('div.books-fold-card-book-item.marrow-book-item').forEach(item => {\n if (shouldSkip(item)) return;\n \n item.style.pointerEvents = 'auto';\n item.style.cursor = 'pointer';\n \n const bookId = item.getAttribute('data-book-id') || item.id;\n if (bookId && !item.hasAttribute('data-click-fixed')) {\n item.setAttribute('data-click-fixed', 'true');\n item.addEventListener('click', (e) => {\n e.preventDefault();\n window.location.href = `https:\/\/fanqienovel.com\/page\/${bookId}`;\n });\n }\n });\n}, 1000);\n\nconst paginationSystem = setInterval(() => {\n const shareBottom = document.querySelector(\".share-bottom-button\");\n const shouldSkip = (el) => {\n return el && (el.closest('.horizontal-slip-modal-card') || \n el.closest('.topic-comment-item.new-card-style'));\n };\n \n if (shareBottom && !shouldSkip(shareBottom)) {\n shareBottom.remove();\n const shareApp = document.querySelector(\".share-end-href-app\");\n const offset = Number(url.searchParams.get('offset') || \"0\");\n \n if (shareApp && !shouldSkip(shareApp)) {\n shareApp.parentNode.innerHTML = `\n <div class=\"share-end-href-app\" style=\"display:flex;justify-content:center;gap:20px;padding:12px 0;\">\n ${offset === 0 ? \"\" : `<a style=\"padding:8px 16px;border-radius:4px;background:#f0f0f0;color:#333;text-decoration:none;\" onclick=\"changePage(${offset-1}, url)\">上一页<\/a>`}\n <span style=\"padding:8px 16px;color:#666;\">${offset+1}<\/span>\n <a style=\"padding:8px 16px;border-radius:4px;background:#f0f0f0;color:#333;text-decoration:none;\" onclick=\"changePage(${offset+1}, url)\">下一页<\/a>\n <\/div>\n `;\n }\n }\n}, 1000);\n\nfunction changePage(value, url) {\n url.searchParams.set('offset', value);\n location.replace(url);\n}\n\nconst style = document.createElement('style');\nstyle.textContent = `\n .page-reader-btn.download:not(.horizontal-slip-modal-card *):not(.topic-comment-item.new-card-style *),\n .download-btn:not(.horizontal-slip-modal-card *):not(.topic-comment-item.new-card-style *),\n .app-download-popup:not(.horizontal-slip-modal-card *):not(.topic-comment-item.new-card-style *) {\n display: none !important;\n visibility: hidden !important;\n pointer-events: none !important;\n opacity: 0 !important;\n }\n \n .topic-comment-item:not(.new-card-style):not(.horizontal-slip-modal-card *) {\n pointer-events: none !important;\n user-select: none !important;\n }\n \n .horizontal-slip-books-item:not(.horizontal-slip-modal-card *):not(.topic-comment-item.new-card-style *),\n div.books-fold-card-book-item.marrow-book-item:not(.horizontal-slip-modal-card *):not(.topic-comment-item.new-card-style *) {\n pointer-events: auto !important;\n cursor: pointer !important;\n }\n`;\ndocument.head.appendChild(style);\n\nsetTimeout(() => {\n clearInterval(adBlockSystem);\n clearInterval(fixBookClick);\n}, 30000);", "jsLib": "var base_url = \t'https:\/\/api.qingtian618.com';\n\nfunction checkSum(s) {\n return String(java.lang.String(s).hashCode())\n}\n\nfunction getKey(key) {\n let parts = key.split(\";\");\n for (let part of parts) {\n if (part.includes(\"qttoken\")) {\n return part.split(\"=\")[1];\n }\n }\n return \"\";\n}\n\nvar javaImport = new JavaImporter();\njavaImport.importClass(\n Packages.android.util.Base64,\n Packages.java.lang.String,\n Packages.java.net.URL,\n Packages.okhttp3.HttpUrl\n);\nwith (javaImport) {\n function btoa(data) {\n return Base64.encodeToString(String(data).getBytes(\"UTF-8\"), 2);\n }\n\n function getSubDomain(url) {\n let baseUrl = getBaseUrl(url);\n if (!baseUrl) {\n return url;\n }\n try {\n let mURL = URL(baseUrl);\n let host = mURL.host;\n if (isIPAddress(host)) return host;\n return HttpUrl.parse(baseUrl).topPrivateDomain() || host;\n } catch (e) {\n this.java.log(e);\n return baseUrl;\n }\n }\n\n function getDomain(url) {\n let baseUrl = getBaseUrl(url);\n if (!baseUrl) {\n return url;\n }\n try {\n return URL(baseUrl).host;\n } catch (e) {\n return baseUrl;\n }\n }\n \/**\n * 移除cookie\n *\/\n function removeCookie(url) {\n const { cookie } = this;\n cookie.removeCookie(url);\n let domains = [getDomain(url), getSubDomain(url)];\n domains.forEach((domain) => {\n cookie.removeCookie(domain);\n });\n }\n}\n\nfunction getBaseUrl(url) {\n if (!url) {\n return null;\n }\n url = String(url);\n if (url.match(\/https?:\\\/\\\/\/i)) {\n var index = url.indexOf(\"\/\", 9);\n return index == -1 ? url : url.substring(0, index);\n }\n return null;\n}\n\nfunction isIPv4Address(ip) {\n ip = String(ip);\n let parts = ip.split(\".\");\n if (parts.length !== 4) return false;\n\n for (let part of parts) {\n if (!\/^\\d+$\/.test(part)) return false; \/\/ 必须是数字\n if (part.length > 1 && part[0] === \"0\") return false; \/\/ 禁止前导零\n let num = parseInt(part, 10);\n if (num < 0 || num > 255) return false; \/\/ 范围检查\n }\n return true;\n}\n\nfunction isIPv6Address(ip) {\n ip = String(ip);\n \/\/ 处理双冒号(最多出现一次)\n if (ip.includes(\":::\")) return false;\n let doubleColonCount = (ip.match(\/::\/g) || []).length;\n if (doubleColonCount > 1) return false;\n\n \/\/ 分割成组\n let groups = ip.split(\":\");\n let validGroupCount = 8;\n let actualGroupCount = groups.filter((g) => g !== \"\").length;\n\n \/\/ 验证组数\n if (doubleColonCount === 1) {\n if (actualGroupCount > validGroupCount - 1) return false;\n } else {\n if (groups.length !== validGroupCount) return false;\n }\n\n \/\/ 验证每组内容\n for (let group of groups) {\n if (group === \"\") continue; \/\/ 跳过空组(双冒号部分)\n if (!\/^[0-9a-fA-F]{1,4}$\/.test(group)) return false; \/\/ 1-4位十六进制\n }\n return true;\n}\n\nfunction isIPAddress(input) {\n return isIPv4Address(input) || isIPv6Address(input);\n}\n\nfunction getSessionId(cookieString) {\n const match = cookieString.match(\/sessionid=([^;]+)\/);\n return match ? match[1] : null;\n}\n", "lastUpdateTime": 0, "loadWithBaseUrl": false, "loginUi": "[{\"name\": \"邮箱\", \"type\": \"text\"},\n {\"name\": \"密码\", \"type\": \"password\"},\n {\n \"name\": \"♥UI登录书源\",\n \"type\": \"button\",\n \"action\": \"login(true)\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n },\n {\n \"name\": \"♥网页登录书源\",\n \"type\": \"button\",\n \"action\": \"loginqt()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n \"name\": \"🍅番茄登录\",\n \"type\": \"button\",\n \"action\": \"webLogin()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n \"name\": \"🔮 检测登录\",\n \"type\": \"button\",\n \"action\": \"checkStatus()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n\n },\n {\n \"name\": \" 🔚 退出登录\",\n \"type\": \"button\",\n \"action\": \"logout()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n \"name\": \" 🗑 清除设备\",\n \"type\": \"button\",\n \"action\": \"clearDevice()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n\n }, {\n \"name\": \"❇️ 书源下载\",\n \"type\": \"button\",\n \"action\": \"renderVersionPage()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n \"name\": \"♻️ 订阅源更新\",\n \"type\": \"button\",\n \"action\": \"renderVersionPageRss()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n },{\n \"name\": \"☕打赏享福利\",\n \"type\": \"button\",\n \"action\": \"vip()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 1\n }\n },\n {\n \ttype: \"text\",\n \tname: \"番茄Token\"\n }\n]", "loginUrl": "const localVersion = '4.7.17';\n\nfunction webLogin() {\n var ck = String(cookie.getKey(\"fanqienovel.com\", \"sessionid\"));\n if (ck && ck != \"\") {\n java.toast(\"\\n\\n请不要重复登录,请先退出登录!\")\n return false\n } \n try {\n java.startBrowserAwait(\"https:\/\/fanqienovel.com\/\", \"登录\")\n } catch (e) { java.toast(e) }\n \n try {\n cookie.removeCookie(\"snssdk.com\")\n } catch (e) {}\n var ck = \"sessionid=\" + String(cookie.getKey(\"fanqienovel.com\", \"sessionid\"));\n let user\n try {\n user = JSON.parse(java.ajax(\"https:\/\/fanqienovel.com\/api\/user\/info\/v2,\" + JSON.stringify({\n method: \"GET\",\n headers: {\n \"Cookie\": ck\n }\n }))).data.name\n } catch (e) {java.log(e)}\n if (!ck || ck == \"sessionid=\" || !user) {\n java.toast(\"\\n\\n未获取到登录凭据,登录失败\")\n cookie.removeCookie(\"fanqienovel.com\")\n return false\n }\n java.toast(\"\\n\\n欢迎 \" + user + \"\\n登录成功!\")\n return true\n}\n\nfunction login(flag) {\n\tif (flag == undefined) {\n\t\tresult = JSON.parse(source.getLoginInfo())\n\t} else {\n\t\tjava.longToast(\"\\n\\n💞正在登录中...\")\n\t\tputLoginInfo(JSON.stringify(result))\n\t}\n\t let register_email = String(result['邮箱'])\n\tlet password = String(result['密码'])\n\tlet key = String(result['密钥']||'')\n\t\n\t\/\/java.log(cookie.getCookie(base_url))\n\tif ((register_email && password || key)&& !String(getKey(String(cookie.getCookie(base_url))))) {\n\t\tremoveCookie(base_url)\n\t\tlet deviceKey = java.webView('', '', 'navigator.userAgent+window.screen.width+window.screen.height')\n\t\tif (String(deviceKey) == \"undefined\") {\n\t\t\ttry {\n\t\t\t\tdeviceKey = java.deviceID();\n\t\t\t} catch (e) {\n\t\t\t\tdeviceKey = java.androidId();\n\t\t\t}\n\t\t};\n\tlet deviceId = java.digestHex(deviceKey, \"SHA256\")\n\t\t if (register_email && password){\n\t\t\tlet options = JSON.stringify({\n\t\t\t\tmethod: 'POST',\n\t\t\t\theaders: {\n\t\t\t\t\t'Content-Type': 'application\/json'\n\t\t\t\t},\n\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\tregister_email: result['邮箱'],\n\t\t\t\t\tpassword: result['密码']\n\t\t\t\t})\n\t\t\t})\n\t\t\ttry {\n\t\t\t\tlet data = JSON.parse(java.ajax(`${base_url}\/login_api,${options}`))\n\t\t\t\tif (data.code == 0) {\n\t\t\t\t\tjava.toast(\"\\n\\n✅️登录成功\")\n\t\t\t\t\tcookie.setCookie(base_url, `qttoken=${data.key};deviceId=${deviceId}`)\n\t\t\t\t\tresult['密钥']=data.key\n\t\t\t\t\tputLoginInfo(JSON.stringify(result))\n\t\t\t\t} else {\n\t\t\t\t\tjava.toast('\\n\\n💔'+data.msg || \"登录失败,请重试!\")\n\t\t\t\t}\n\t\t\t} catch (e) {\n\t\t\t\tjava.toast(\"\\n\\n💔登录失败,请重试!\\n\" + e.message)\n\t\t\t}\n\t\t} else {\n\t\t\tcookie.setCookie(base_url, `qttoken=${key};deviceId=${deviceId}`)\n\t\t\tlet res=java.ajax(`${base_url}\/user_api,{\"method\":\"POST\",\"headers\":{\"cookie\":\"${cookie.getCookie(base_url)}\"}}`)\t\t\t\n\t\t\ttry {\n\t\t\t\tres=JSON.parse(res)\n\t\t\t\tif (res.id!=undefined) {\n\t\t\t\t\tjava.toast('\\n\\n密钥登录成功')\n\t\t\t\t\tresult['邮箱'] = res.email\n\t\t\t\t\tputLoginInfo(JSON.stringify(result))\n\t\t\t\t} else {\n\t\t\t\t\tthrow new Error()\n\t\t\t\t}\n\t\t\t} catch(e) {\n\t\t\t\tjava.log(e)\n\t\t\t\tjava.toast(\"\\n\\n💔登录失败\")\n\t\t\t}\n\t\t}\n\t} else if (flag&&String(getKey(String(cookie.getCookie(base_url))))) {\n\t\tjava.toast(\"\\n\\n当前✅️已登录,请🚫退出登录后重新登录\");\n\t\t\/\/checkStatus();\n\t} else if (flag) {\n\t\tjava.toast(\"\\n\\n⛔️请先填写邮箱和密码\");\n\t\t}\n}\n\nfunction checkStatus() {\n\tjava.longToast('\\n\\n♻️检测中...');\n\tlet res=java.ajax(`${base_url}\/user_api,{\"method\":\"POST\",\"headers\":{\"cookie\":\"${cookie.getCookie(base_url)}\"}}`)\t\n\ttry {\n\t\t\t\tres=JSON.parse(res)\n\t\t\t\tif (res.id!=undefined) {\n\t\t\t\t\tresult['邮箱'] = res.email\n\t\t\t\t\tputLoginInfo(JSON.stringify(result))\n\t\t\t\t\tlet devices\n\t\t\t\t\ttry {\n\t\t\t\t\t\tdevices = JSON.parse(res.device).length;\n\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\tdevices = res.device ? 1 : 0;\n\t\t\t\t\t}\n\t\t\t\t\tlet isVip;\n\t\t\t\t\tif (res.is_vip==1) {\n\t\t\t\t\t\tisVip = 'VIP';\n\t\t\t\t\t} else if (res.is_vip>=2) {\n\t\t\t\t\t\tisVip = 'SVIP';\n\t\t\t\t\t} else {\n\t\t\t\t\t\tisVip = '普通会员';\n\t\t\t\t\t}\n\t\t\t\t\ttips= `\n┏┅┅┅┅┅┅┱┄┄┄┄┄┄┄┄┄┄┐\n ✉️邮箱 ${res.email.replace(\/(.{3}).*?@\/,\"$1***@\").padEnd(20,\"\\t\")}\n┣┅┅┅┅┅┅╉┄┄┄┄┄┄┄┄┄┄┤\n 🔑密钥 ${(`${res.user_key.substring(0,4)}***${res.user_key.slice(-4)}`).padEnd(20,\"\\t\")}\n┣┅┅┅┅┅┅╉┄┄┄┄┄┄┄┄┄┄┤\n 📅注册时间 ${java.timeFormat(res.register_time*1000).padEnd(20,\"\\t\")}\n┣┅┅┅┅┅┅╉┄┄┄┄┄┄┄┄┄┄┤\n 🗒️今日阅读 ${(java.timeFormat(new Date()).slice(0,10)==java.timeFormat(res.last_read_time * 1000).slice(0,10)?res.day_read_count:0).toString().padEnd(20,\"\\t\")}\n┣┅┅┅┅┅┅╉┄┄┄┄┄┄┄┄┄┄┤\n 📚累计阅读 ${res.all_read_count.toString().padEnd(20,\"\\t\")}\n┣┅┅┅┅┅┅╉┄┄┄┄┄┄┄┄┄┄┤\n 🕓最后阅读 ${(res.last_read_time != 0?java.timeFormat(res.last_read_time * 1000):'未阅读').padEnd(20,\"\\t\")}\n┣┅┅┅┅┅┅╉┄┄┄┄┄┄┄┄┄┄┤\n 📱关联设备 ${devices.toString().padEnd(20,\"\\t\")}\n┣┅┅┅┅┅┅╉┄┄┄┄┄┄┄┄┄┄┤\n 👑会员状态 ${isVip.padEnd(20,\"\\t\")}\n┣┅┅┅┅┅┅╉┄┄┄┄┄┄┄┄┄┄┤\n 🚫封禁状态 ${res.is_banned?'已封禁':'正常 '} \n┗┅┅┅┅┅┅┹┄┄┄┄┄┄┄┄┄┄┘\n`\n\t\t\t\t\tjava.log(tips)\n\t\t\t\t\tjava.longToast(tips)\n\t\t\t\t} else {\n\t\t\t\t\tthrow new Error(res.msg)\n\t\t\t\t}\n\t\t\t} catch(e) {\n\t\t\t\t\/\/java.log(e)\n\t\t\t\tjava.toast(\"\\n检测登录失败\\n\"+e.message)\n\t\t\t}\n}\n\nfunction clearDevice() {\n let res=java.ajax(`${base_url}\/clear,{\"method\":\"POST\",\"headers\":{\"cookie\":\"${cookie.getCookie(base_url)}\"}}`)\n\t\/\/java.toast(res.code === 0 ? \"\\n\\n📴设备清除成功\" : res.msg)\n\tPackages.java.lang.Thread.sleep(500)\n\tcheckStatus()\n}\n\/\/ 保存登录UI信息\nfunction putLoginInfo(info) {\n\ttry {\n\t\tlet key = java.androidId()\n\t\tlet encodeStr = Packages.android.util.Base64.encodeToString(java.createSymmetricCrypto(\"AES\", key).encrypt(info), 2)\n\t\tcache.put(`userInfo_${source.getKey()}`, encodeStr)\n\t\treturn true\n\t} catch (e) {\n\t\tjava.log(e)\n\t\treturn source.putLoginInfo(info)\n\t}\n}\n\n\n\nfunction api() {\njava.startBrowserAwait('http:\/\/vip.qingtian618.com', \"首页\");\n}\n\n\n\/\/打赏\nfunction vip() {\n\tjava.startBrowserAwait(base_url+ '\/coffee', \"喝咖啡\");\n\t\n}\nfunction loginqt() {\n\tjava.startBrowserAwait(base_url + '\/login', '登录晴天小说书源');\n}\n\n function logout() {\n\tcookie.removeCookie(\"fanqienovel.com\");\n\tcookie.removeCookie(\"snssdk.com\");\n\tcookie.removeCookie(base_url);\n\tcookie.removeCookie(\"qingtian618.cn\");\n\tcookie.removeCookie(\"113.45.175.112\");\n\tjava.toast(\"退出登录成功\");\n}\n\nfunction renderVersionPage() {\n\tjava.longToast(\"\\n\\n请点击网页登录-书源下载进行下载\/更新\");\n\t}\n\t\n\t\/\/ 订阅源更新\nfunction renderVersionPageRss() {\n\tlet yd = '';\n let html = `\n<!DOCTYPE html>\n<html lang=\"zh-CN\">\n<head>\n <meta charset=\"UTF-8\" \/>\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" \/>\n <title>订阅源更新<\/title>\n <style>\n body {\n margin: 0;\n padding: 1em;\n font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;\n background: linear-gradient(135deg, #e0c3fc 0%, #8ec5fc 100%);\n display: flex;\n justify-content: center;\n align-items: center;\n min-height: 100vh;\n color: #333;\n }\n\n .container {\n width: 100%;\n max-width: 480px;\n background: rgba(255, 255, 255, 0.85);\n backdrop-filter: blur(10px);\n border-radius: 16px;\n padding: 1em;\n box-shadow: 0 8px 30px rgba(0, 0, 0, 0.1);\n animation: fadeIn 0.5s ease-in-out;\n display: none; \n }\n\n @keyframes fadeIn {\n from { opacity: 0; transform: translateY(10px); }\n to { opacity: 1; transform: translateY(0); }\n }\n\n h1 {\n text-align: center;\n font-size: 1.6em;\n margin-bottom: 1em;\n color: #333;\n }\n\n .version-info, .status {\n text-align: center;\n margin-bottom: 1em;\n font-weight: 500;\n }\n\n .status {\n color: #d63384;\n font-size: 0.9em;\n }\n\n .button-group {\n display: flex;\n flex-direction: column;\n gap: 0.75em;\n margin-bottom: 1.5em;\n }\n\n .button {\n display: block;\n width: 90%;\n padding: 0.85em;\n text-align: center;\n font-size: 1em;\n border: none;\n border-radius: 8px;\n text-decoration: none;\n background: linear-gradient(135deg, #42e695 0%, #3bb2b8 100%);\n color: white;\n font-weight: bold;\n transition: all 0.25s ease;\n box-shadow: 0 4px 14px rgba(0, 0, 0, 0.15);\n }\n\n .button:hover {\n transform: translateY(-2px);\n box-shadow: 0 6px 20px rgba(0, 0, 0, 0.2);\n }\n\n .logs {\n background: #ffffffcc;\n padding: 1em;\n border-radius: 8px;\n border: 1px solid #ddd;\n box-shadow: inset 0 1px 3px rgba(0,0,0,0.05);\n }\n\n .logs h2 {\n font-size: 1.2em;\n margin-bottom: 0.8em;\n border-bottom: 1px solid #ccc;\n padding-bottom: 0.3em;\n color: #222;\n }\n\n .log-item {\n margin-bottom: 0.7em;\n line-height: 1.5;\n }\n\n .log-item-date {\n font-weight: bold;\n color: #3b3b3b;\n }\n\n .log-item-content {\n margin-left: 1em;\n color: #555;\n }\n\n .loading {\n text-align: center;\n font-size: 1.1em;\n color: #555;\n animation: pulse 1.2s infinite;\n }\n\n @keyframes pulse {\n 0% { opacity: 1; }\n 50% { opacity: 0.5; }\n 100% { opacity: 1; }\n }\n .ad-banner {\n background: linear-gradient(135deg, #ff9a9e 0%, #fad0c4 100%);\n color: white;\n padding: 10px 15px;\n text-align: center;\n margin-bottom: 20px;\n cursor: pointer;\n border-radius: 8px;\n border: 1px solid #ddd;\n box-shadow: inset 0 1px 3px rgba(0,0,0,0.05);\n }\n\n .ad-banner:hover {\n transform: translateY(-2px);\n box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);\n }\n\n .ad-banner span {\n font-weight: 500;\n font-size: 16px;\n }\n <\/style>\n<\/head>\n<body>\n <div id=\"loading\" class=\"loading\">🔍 正在拼命检查中,请稍候~ 📡<\/div>\n\n <div class=\"container\" id=\"container\">\n <h1>♻️ 订阅源版本检查<\/h1>\n <div class=\"ad-banner\" onclick=\"window.location.href='https:\/\/api.qingtian618.com\/phonecardad'\">\n <span>📱 手机卡充值优惠活动,点击查看详情 →<\/span>\n <\/div>\n <div class=\"version-info\" id=\"versionInfo\">加载中...<\/div>\n <div class=\"status\" id=\"statusText\"><\/div>\n\n <div class=\"button-group\" id=\"buttonGroup\" style=\"display: none;\">\n <\/div>\n\n <div class=\"logs\" id=\"logs\" style=\"display: none;\">\n <h2>📝 更新日志<\/h2>\n <div id=\"logList\"><\/div>\n <\/div>\n <\/div>\n\n <script>\n(async function() {\n const loading = document.getElementById('loading');\n const container = document.getElementById('container');\n const versionInfo = document.getElementById('versionInfo');\n const statusText = document.getElementById('statusText');\n const updateButton = document.getElementById('updateButton');\n const buttonGroup = document.getElementById('buttonGroup');\n const logsContainer = document.getElementById('logs');\n const logList = document.getElementById('logList');\n\nconst localVer = '${String(localVersion)}';\n\/\/ 版本判断逻辑\nfunction compareVersions(vs) {\n const parts1 = localVer.split('.').map(Number);\n const parts2 = vs.split('.').map(Number);\n const maxLength = Math.max(parts1.length, parts2.length);\n for (let i = 0; i < maxLength; i++) {\n const num1 = parts1[i] || 0;\n const num2 = parts2[i] || 0;\n if (num1 > num2) return 1;\n if (num1 < num2) return -1;\n }\n return 0;\n}\n\nconst encodedEndpoints = [\n 'aHR0cHM6Ly9hcGkucWluZ3RpYW42MTguY29t', \n 'aHR0cHM6Ly92MS5xaW5ndGlhbjYxOC5jb20=', \n 'aHR0cHM6Ly92Mi5xaW5ndGlhbjYxOC5jb20=', \n 'aHR0cHM6Ly92My5xaW5ndGlhbjYxOC5jb20=', \n 'aHR0cHM6Ly92NC5xaW5ndGlhbjYxOC5jb20=', \n 'aHR0cHM6Ly92NS5xaW5ndGlhbjYxOC5jb20=', \n 'aHR0cDovLzExMy40NS4xNzUuMTEyOjg4ODg=' \n];\n\nfunction decodeEndpoint(str) {\n return atob(str);\n}\n\nasync function fetchVersionData() {\n for (const b64 of encodedEndpoints) {\n const url = decodeEndpoint(b64);\n try {\n const response = await fetch(url + '\/version', { timeout: 2000 }); \n if (response.ok) {\n return await response.json();\n }\n } catch (e) {\n console.warn(\\`接口失败:\\${url}\\`, e);\n }\n }\n throw new Error('所有更新接口都请求失败');\n}\n try {\n loading.style.display = 'block';\n\n const data = await fetchVersionData();\n const cloudVersion = String(data.rssVersion3);\n const updateLog = data.update_rss_log || {};\n\n versionInfo.innerHTML = \\`🔖当前版本:v\\${localVer} <br> 🔭最新版本:v\\${cloudVersion}\\`;\n\n logList.innerHTML = Object.entries(updateLog).map(([date, content]) => \\`\n <div class=\"log-item\">\n <div class=\"log-item-date\">\\${date}<\/div>\n <div class=\"log-item-content\">\\${content}<\/div>\n <\/div>\n \\`).join('');\n logsContainer.style.display = 'block';\n\n if (compareVersions(cloudVersion) === -1) {\n statusText.innerHTML = '<span>✨ 有新版本可用,建议立即更新!<br>${yd}<\/span>';\nconst domainMap = {\n main: 'aHR0cDovL3N5LnFpbmd0aWFuNjE4LmNvbQ==', \n d1: 'aHR0cHM6Ly9hcGkucWluZ3RpYW42MTguY29t', \n d2: 'aHR0cHM6Ly92MS5xaW5ndGlhbjYxOC5jb20=',\n d3: 'aHR0cHM6Ly92Mi5xaW5ndGlhbjYxOC5jb20=',\n d4: 'aHR0cHM6Ly92My5xaW5ndGlhbjYxOC5jb20=',\n d5: 'aHR0cHM6Ly92NC5xaW5ndGlhbjYxOC5jb20=',\n d6: 'aHR0cHM6Ly92NS5xaW5ndGlhbjYxOC5jb20='\n};\n\nfunction decode(b64) {\n return atob(b64);\n}\n\nconst path = '\/sy\/download\/晴天订阅源.json';\nconst mainPath = '\/download\/晴天订阅源.json';\n\nconst routes = [\n { name: '🚀 主线路', domain: 'main', suffix: mainPath },\n { name: '📦 备用线路1', domain: 'd1', suffix: path },\n { name: '🛰️ 备用线路2', domain: 'd2', suffix: path },\n { name: '🛰️ 备用线路3', domain: 'd3', suffix: path },\n { name: '🛰️ 备用线路4', domain: 'd4', suffix: path },\n { name: '🛰️ 备用线路5', domain: 'd5', suffix: path },\n { name: '🛰️ 备用线路6', domain: 'd6', suffix: path }\n];\n\nbuttonGroup.innerHTML = routes.map(r => {\n const fullUrl = decode(domainMap[r.domain]) + r.suffix;\n return \\`<a href=\"legado:\/\/import\/auto?src=\\${encodeURIComponent(fullUrl)}\" class=\"button\">\\${r.name}<\/a>\\`;\n}).join('');\n buttonGroup.style.display = 'flex';\n } else {\n statusText.textContent = '✅ 已是最新订阅源咯~';\n }\n\n loading.style.display = 'none';\n container.style.display = 'block';\n\n } catch (err) {\n loading.textContent = '😢 检查失败啦~ 请稍后再试==>'+err;\n console.error('版本检查失败:', err);\n }\n})();\n<\/script>\n<\/body>\n<\/html>\n`;\n java.startBrowser(`data:text\/html;base64,${java.base64Encode(html)}`, '晴天订阅源更新');\n}\n", "ruleArticles": "<js>\nif (baseUrl.includes('番茄')) {\nlet ck = (String(cookie.getKey(\"fanqienovel.com\", \"sessionid\")) ? String(cookie.getKey(\"fanqienovel.com\", \"sessionid\")) : (source.getLoginInfoMap() || {})['番茄Token']) || \"\";\nfunction getCellId() {\n let sInfo = java.ajax(java.log(base_url + \"\/book_mall_tab?ssionid=\" + ck));\n let cell_id = \"cell_id%253D6914906572011339784%2526\"\n let body = JSON.parse(sInfo).data.tab_item[0].cell_data[1].cell_data\n for (let i of body) {\n \/*java.log(JSON.stringify(i))*\/\n if (typeof i.cell_url !== 'undefined') {\n \/\/ java.log(i.cell_url)\n cell_id = i.cell_url\n break\n }\n }\n java.log(cell_id)\n cell_id = cell_id.split(\"cell_id%253D\")[1].split(\"%2526\")[0]\n java.log(cell_id)\n return cell_id\n}\nlet cell_id = getCellId();\njava.ajax(java.log(base_url + '\/book_mall_y?cell_id=' + cell_id + '&ssionid='+ck));\n} else {\n\t\/\/java.toast(baseUrl);\n\tlet other = {\n\t\t'data':{'cell_view':{'topic_data':\n\t\t[{'topic_desc':{\n\t\t\t'topic_title':'进入官网',\n\t\t\t 'topic_id':baseUrl\n\t\t\t}}]}\n\t\t}}\n\t\tother = JSON.stringify(other);\n\t\t\/\/java.toast(other)\n\t};\n\t<\/js>\n$.data.cell_view.topic_data[*]", "ruleImage": "$.topic_desc.topic_cover", "ruleLink": "<js>\nlet ruleUrl;\nif (baseUrl.includes('番茄')) { \n\truleUrl = `https:\/\/reading.snssdk.com\/wap\/topic-share.html?topic_id={{$.topic_desc.topic_id}}&sort=smart_hot&service_id=6&session_id=0&aid=1967`\n\t} else {\n\t\truleUrl = '{{$.topic_desc.topic_id}}';\n\t\t\/\/java.toast(ruleUrl)\n\t\t}\n\truleUrl\n<\/js>", "rulePubDate": "$.topic_desc.topic_content\n@js:\nif (baseUrl.includes('番茄')) {\n\tlet data = JSON.parse(result).skeleton.data.replace(\/<\\\/?search_link>\/g, \" \")\nlet select = Packages.org.jsoup.Jsoup.parse(data).select(\"p, span\")\nlet final = Array.from(select).map(p => p.text()).join(\" \")\njava.timeFormat(java.getString('topic_desc.create_time')*1000) + (!Packages.android.text.TextUtils.isEmpty(final) ? (' | ' + final) : '')\n}", "ruleTitle": "$.topic_desc.topic_title", "shouldOverrideUrlLoading": "function extractBookId(url) {\n let match = url.match(\/[?&]book_id=([^&]+)\/) || url.match(\/page\\\/(\\d+)\/) || url.match(\/shuku\\\/(\\d+_\\d+|\\d+)(?:-\\d+)?\/) || url.match(\/query\\\/(\\d+)\/) || url.match(\/book\\\/(\\d+)\/) || url.match(\/album\\\/(\\d+)\/) || url.match(\/reader\\\/(\\d+)\/) || url.match(\/book-detail\\\/(\\d+)\/);\n \n if (!match) {\n \t return null;\n }\n const bookId = match[1];\n return bookId;\n}\n\n\/\/ java.toast(url.startsWith('legadosearch:\/\/'))\nif (url.startsWith('legadosearch:\/\/')) { \n \/\/ java.toast('ab')\n java.searchBook(\n decodeURIComponent(\n url.replace('legadosearch:\/\/', '')\n )\n )\n}\nlet needBreak = false\nfunction isNewLegado() {\n try {\n return !!Packages.io.legado.app.ui.book.manga;\n } catch(e) {\n return false; \n } \n}\nif (isNewLegado()) {\n\t if (url.match(\/book_id=\\d+\/) || url.match(\/\\\/page\\\/\\d+\/) || url.match(\/shuku\\\/(\\d+_\\d+|\\d+)(?:-\\d+)?\/) || url.match(\/query\\\/(\\d+)\/) || url.match(\/book\\\/(\\d+)\/) || url.match(\/album\\\/(\\d+)\/) || url.match(\/reader\\\/(\\d+)\/) || url.match(\/book-detail\\\/(\\d+)\/)) {\n \t\/\/java.toast(url);\n \t const bookId = java.encodeURI(java.base64Encode(extractBookId(url)));\n \t \/\/java.toast(bookId);\n \t let url2 = `https:\/\/api.qingtian618.com\/detail?book_id=${bookId}&source=%e7%95%aa%e8%8c%84`\n \t if (url.includes('shuku')) {\n \t \turl2 = `https:\/\/api.qingtian618.com\/detail?book_id=${bookId}&source=七猫`\n \t \t}\n \t \tif (url.includes('tadu')) {\n \t \turl2 = `https:\/\/api.qingtian618.com\/detail?book_id=${bookId}&source=塔读`\n \t \t}\n \t \tif (url.includes('shuqi')) {\n \t \turl2 = `https:\/\/api.qingtian618.com\/detail?book_id=${bookId}&source=书旗`\n \t \t}\n \t \tif (url.includes('ximalaya')) {\n \t \turl2 = `https:\/\/api.qingtian618.com\/detail?book_id=${bookId}&source=喜马拉雅&tab=听书`\n \t \t}\n \t \tif (url.includes('qq')) {\n \t \turl2 = `https:\/\/api.qingtian618.com\/detail?book_id=${bookId}&source=QQ`\n \t \t}\n \t \/\/java.toast(url2.replace('%3D',''));\n \t java.addBook(url2.replace('%3D',''));\n needBreak = true;\n }\n}\n!(url.startsWith('http') || url.startsWith('legado')) || url.match(\/\\\/chapter-list\\\/\\d+\/) || needBreak", "singleUrl": false, "sortUrl": "番茄::番茄\n七猫::https:\/\/www.qimao.com\/\n塔读::https:\/\/m.tadu.com\/\n书旗::https:\/\/t.shuqi.com\/", "sourceComment": "原作者 未知\n二改:\n 世界有多大 (翻页)\n 梓澄qwq (个人推荐, js注入, 拦截跳转)\n Folltoshe (js注入)\n 柚屿 (完善登录)\n 清词 (失效修复,修复有书籍无法点击跳转的情况,加强过滤)\n 期待完美的自己(兼容晴天完全版书源)\n 晴天(完善晴天订阅源)", "sourceIcon": "https:\/\/api.qingtian618.com\/favicon.ico", "sourceName": "晴天书荒广场", "sourceUrl": "https:\/\/www.baidu.com\/晴天番茄书荒广场" }