diff --git a/.env.development b/.env.development index 6edf51d2..6aba1651 100644 --- a/.env.development +++ b/.env.development @@ -34,6 +34,7 @@ QQ_SMTP_AUTH_CODE = # 调试猫源-推荐开启 CAT_DEBUG=1 +PHP_PATH= PYTHON_PATH= VIRTUAL_ENV= daemonMode=0 diff --git a/.gitignore b/.gitignore index 28c6287f..7bbca429 100644 --- a/.gitignore +++ b/.gitignore @@ -161,3 +161,4 @@ dist /apps/salary/ /jx/_30wmv.js .DS_Store +/spider/catvod/mtv60w[差].js diff --git a/.plugins.example.js b/.plugins.example.js index c61674e6..6b429d01 100644 --- a/.plugins.example.js +++ b/.plugins.example.js @@ -35,14 +35,14 @@ const plugins = [ path: 'plugins/pup-sniffer', // 插件路径 params: '-port 57573', // 启动参数:端口57573 desc: 'drplayer嗅探服务', // 插件描述:提供视频适配代理功能 - active: true // 是否激活:true表示启用此插件 + active: false // 是否激活:true表示启用此插件 }, { name: 'mediaProxy', // 插件名称 path: 'plugins/mediaProxy', // 插件路径 params: '-port 57574', // 启动参数:端口57574 desc: 'go媒体代理服务', // 插件描述:提供视频适配代理功能 - active: true // 是否激活:true表示启用此插件 + active: false // 是否激活:true表示启用此插件 }, ] diff --git a/Dockerfile b/Dockerfile index 0377fc19..63bb1162 100644 --- a/Dockerfile +++ b/Dockerfile @@ -43,13 +43,28 @@ RUN cp /app/.env.development /app/.env && \ # 但是,我们仍然需要安装Node.js运行时本身(除非drpys项目是一个纯静态资源服务,不需要Node.js运行时) RUN apk add --no-cache nodejs +# 安装php8.3及其扩展 +RUN apk add --no-cache \ + php83 \ + php83-cli \ + php83-curl \ + php83-mbstring \ + php83-xml \ + php83-pdo \ + php83-pdo_mysql \ + php83-pdo_sqlite \ + php83-openssl \ + php83-sqlite3 \ + php83-json +RUN ln -sf /usr/bin/php83 /usr/bin/php + # 安装python3依赖 RUN apk add --no-cache python3 \ py3-pip \ py3-setuptools \ py3-wheel -# 激活python3虚拟环境并安装依pip3赖 +# 激活python3虚拟环境并安装requirements依赖 RUN python3 -m venv /app/.venv && \ . /app/.venv/bin/activate && \ pip3 install -r /app/spider/py/base/requirements.txt diff --git a/README.md b/README.md index 3cbdcc29..558b3c0e 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,8 @@ nodejs作为服务端的drpy实现。全面升级异步写法 ### 常用超链接 * [本项目主页-免翻](https://github.com/hjdhnx/drpy-node) -* [最新DS本地包-适配皮卡丘](/gh/release) +* ~~[最新DS本地包-适配皮卡丘](/gh/release)~~ +* [DS本地包下载中心](/admin/download) * [接口文档](docs/apidoc.md) | [接口列表如定时任务](docs/apiList.md) | ~~[小猫影视-待对接T4](https://github.com/waifu-project/movie/pull/135)~~ * [代码质量评估工具说明](docs/codeCheck.md) | [DS项目代码评估报告](docs/codeCheckReport.md) @@ -19,9 +20,11 @@ nodejs作为服务端的drpy实现。全面升级异步写法 * [本地配置接口-动态外网/局域网](/config/1?healthy=1&pwd=$pwd) * [其他配置接口-订阅过滤](/docs/sub.md) * [python环境](/docs/pyenv.md) | [DS项目环境变量说明](/docs/envdoc.md) +* php环境(详见 spider/php/readme.md) 不在这里赘述 * [猫源调试教程](/docs/catDebug.md) * [接口压测教程](/docs/httpTest.md) * [AI编程工具 trae](https://www.trae.ai/account-setting#subscription) | 邮编ZIP输入: 518000 +* [推荐使用AI模型-GLM4.7](https://www.bigmodel.cn/glm-coding?ic=DRV3C8M5NX) | [GLM配置文档](https://docs.bigmodel.cn/cn/coding-plan/tool/trae) * [免费AI-360纳米](https://bot.n.cn/)|[免费AI-当贝AI](https://ai.dangbei.com/chat)|[国外聚合全模型](https://lmarena.ai/) * [本站防止爬虫协议](/robots.txt) * [油猴脚本-反切屏检测](/public/monkey/check_screen_leave.user.js) @@ -44,6 +47,7 @@ nodejs作为服务端的drpy实现。全面升级异步写法 * [DS时钟插件-白色时钟](/apps/clock/white_clock.html)|[日历时钟](/apps/clock/index.html) * [DS庆祝页面-完结撒花](/apps/happy/index.html) * [bookReader](/apps/book-reader) +* [系统备份与恢复](/apps/backup-restore/index.html) * [代码加解密工具](/admin/encoder) * [央视点播解析工具](/proxy/央视大全[官]/index.html) * [在线猫ds源主页](/cat/index.html) @@ -66,21 +70,21 @@ nodejs作为服务端的drpy实现。全面升级异步写法 ## 更新记录 -### 20260118 +### 20260131 -更新至V1.3.18 +更新至V1.3.21 -### 20260115 +### 20260127 -更新至V1.3.17 +更新至V1.3.20 -### 20260113 +### 20260125 -更新至V1.3.16 +更新至V1.3.19 -### 20260112 +### 20260118 -更新至V1.3.15 +更新至V1.3.18 [点此查看完整更新记录](docs/updateRecord.md) diff --git a/apps/backup-restore/index.html b/apps/backup-restore/index.html new file mode 100644 index 00000000..98a03231 --- /dev/null +++ b/apps/backup-restore/index.html @@ -0,0 +1,373 @@ + + + + + + 系统备份与恢复 + + + +
+

📂 系统备份与恢复

+ +
+
+

备份说明: 点击“立即备份”将系统配置、插件及脚本备份到同级 backup 目录。

+

恢复说明: 点击“恢复备份”将从 backup 目录恢复文件覆盖当前系统。

+
+ +
+
+ 涉及文件清单 + 只读 +
+
+ 加载中... +
+
+
上次备份:--
+
上次恢复:--
+
+
+
+ +
+ + +
+ +
+
系统准备就绪,等待操作...
+
+
+ + + + diff --git a/config/map.txt b/config/map.txt index 13760290..21d1298f 100644 --- a/config/map.txt +++ b/config/map.txt @@ -29,6 +29,8 @@ UC分享@@?type=url¶ms=../json/UC分享.json@@UC分享[盘] 网盘[模板]@@?type=url¶ms=../json/域名配置.json$多多@@多多ᵐ[盘] 网盘[模板]@@?type=url¶ms=../json/域名配置.json$欧歌@@欧歌ᵐ[盘] 网盘[模板]@@?type=url¶ms=../json/域名配置.json$至臻@@至臻ᵐ[盘] +AppFox@@https://qh.70qh.top@@麒麟[AFX] +AppFox@@http://host1.sopython.top/host.json@@粉象[AFX] AppFox@@http://app.hktvyb.cc@@TVB云播[AFX] AppFox@@{"host":"http://www.yezitv.top/dtym.json"}@@木瓜影视[AFX] AppFox@@{"host":"http://nico.oiio.fun"}@@花柳影视[AFX] diff --git a/config/parses.conf b/config/parses.conf index f150792d..95a61601 100644 --- a/config/parses.conf +++ b/config/parses.conf @@ -3,13 +3,14 @@ # 名称,链接,类型,ua,flag (ua不填默认 Mozilla/5.0) 可以手动填 Dart/2.14 (dart:io) # JSON解析排前面 -J1,https://zy.qiaoji8.com/gouzi.php?url=,1 -J2,https://jxjson.icu/neibu.php?url=,1 +J1,https://kalbim.xatut.top/kalbim2025/781718/play/video_player.php?url=,1 +J2,http://sspa8.top:8100/api/?key=1060089351&url=,1 # J3,http://pan.qiaoji8.com/tvbox/neibu.php?url=,1 # J4,http://yunhai.qijiyun.vip/home/api?type=ys&uid=177259&key=dijnouxKNOQSTUWXY5&url=,1 J芒果4k,http://mg.itufm.top/mg.php?url=,1 -HGvip,http://1.94.221.189:88/algorithm.php?url=,1 # J皮皮虾,http://45.207.215.101:5423/index.php?url=,1 +J腾讯关姐,{{hostName}}:5759/tencent.php/?url=,1 +# 295关姐,{{hostName}}:5759/295yun.php?url=,1 # WEB解析放后面 W花旗,https://www.huaqi.live/?url= @@ -18,15 +19,18 @@ W盘古,https://www.playm3u8.cn/jiexi.php?url= # W虾米,https://jx.xmflv.com/?url= # W无双,http://103.117.123.193:1980/players/?url= W1,https://jx.xymp4.cc/?url= -# W2,https://cdn.zyc888.top/?url= -# W3,https://yparse.ik9.cc/index.php?url= -# W4,https://jx.yparse.com/index.php?url= -# W5,https://jx.2s0.cn/player/?url= -# W6,https://jx.quankan.app/?url= +#W2,https://im1907.top/?jx= +W3,https://yparse.ik9.cc/index.php?url= +W4,https://jiexi.site/?url= +W5,https://jx.m3u8.tv/jiexi/?url= +W7,https://www.pangujiexi.com/jiexi/?url= +W8,https://www.pouyun.com/?url= +W9,https://jx.xmflv.com/?url= +Wa,https://jx.xmflv.cc/?url= +Wb,https://jx.yparse.com/index.php?url= +Wc,https://www.8090g.cn/?url= # W7,https://jx.aidouer.net/?url= # W8,https://www.8090g.cn/?url= # W9,https://jx.yangtu.top?url= # W10,https://jx.m3u8.tv/jiexi/?url= -W11,https://www.ckplayer.vip/jiexi/?url= -腾讯关姐,{{hostName}}:5759/tencent.php/?url=,1 -295关姐,{{hostName}}:5759/295yun.php?url=,1 +Wz,https://www.ckplayer.vip/jiexi/?url= diff --git a/controllers/api.js b/controllers/api.js index 1f24be45..6e878bd8 100644 --- a/controllers/api.js +++ b/controllers/api.js @@ -24,16 +24,18 @@ import {validatePwd} from "../utils/api_validate.js"; import {startJsonWatcher, getApiEngine} from "../utils/api_helper.js"; import * as drpyS from '../libs/drpyS.js'; import hipy from '../libs/hipy.js'; +import php from '../libs/php.js'; import xbpq from '../libs/xbpq.js'; import catvod from '../libs/catvod.js'; /** * 支持的引擎映射表 - * 包含drpyS、hipy、xbpq、catvod四种引擎 + * 包含drpyS、hipy、phipy、xbpq、catvod五种引擎 */ const ENGINES = { drpyS, hipy, + php, xbpq, catvod, }; diff --git a/controllers/config.js b/controllers/config.js index 1b8aae62..3fa9b978 100644 --- a/controllers/config.js +++ b/controllers/config.js @@ -23,6 +23,7 @@ import {validateBasicAuth, validatePwd} from "../utils/api_validate.js"; import {getSitesMap} from "../utils/sites-map.js"; import {getParsesDict} from "../utils/file.js"; import batchExecute from '../libs_drpy/batchExecute.js'; +import {isPhpAvailable} from '../utils/phpEnv.js'; const {jsEncoder} = drpyS; @@ -76,6 +77,7 @@ async function generateSiteJSON(options, requestHost, sub, pwd) { const jsDir = options.jsDir; const dr2Dir = options.dr2Dir; const pyDir = options.pyDir; + const phpDir = options.phpDir; const catDir = options.catDir; const configDir = options.configDir; const jsonDir = options.jsonDir; @@ -417,6 +419,11 @@ async function generateSiteJSON(options, requestHost, sub, pwd) { filterable: 1, // 固定值 quickSearch: 1, // 固定值 }; + if (baseName.includes('[画]')) { + ruleObject.类型 = '漫画' + } else if (baseName.includes('[书]')) { + ruleObject.类型 = '小说' + } let ruleMeta = {...ruleObject}; const filePath = path.join(pyDir, file); const header = await FileHeaderManager.readHeader(filePath); @@ -490,6 +497,68 @@ async function generateSiteJSON(options, requestHost, sub, pwd) { await batchExecute(py_tasks, listener); } + + // 根据用户是否启用php源去生成对应配置 + const enable_php = ENV.get('enable_php', '1'); + console.log('isPhpAvailable:', isPhpAvailable); + if ((enable_php === '1' && isPhpAvailable) || enable_php === '2') { + const php_files = readdirSync(phpDir); + const api_type = enable_php === '2' ? 3 : 4; + let php_valid_files = php_files.filter((file) => file.endsWith('.php') && !file.startsWith('_') && !['config.php', 'index.php', 'test_runner.php'].includes(file)); + log(`开始生成php的T${api_type}配置,phpDir:${phpDir},源数量: ${php_valid_files.length}`); + + const php_tasks = php_valid_files.map((file) => { + return { + func: async ({file, phpDir, requestHost, pwd, SitesMap}) => { + const baseName = path.basename(file, '.php'); + let api = enable_php === '2' ? `${requestHost}/php/${file}` : `${requestHost}/api/${baseName}?do=php`; + let ext = ''; + if (pwd) { + api += enable_php === '2' ? `?pwd=${pwd}` : `&pwd=${pwd}`; + } + let ruleObject = { + searchable: 1, + filterable: 1, + quickSearch: 1, + }; + if (baseName.includes('[画]')) { + ruleObject.类型 = '漫画' + } else if (baseName.includes('[书]')) { + ruleObject.类型 = '小说' + } + let ruleMeta = {...ruleObject}; + const filePath = path.join(phpDir, file); + + Object.assign(ruleMeta, { + title: baseName, + lang: 'php', + }); + ruleMeta.title = enableRuleName ? ruleMeta.title || baseName : baseName; + + let fileSites = []; + let key = `php_${ruleMeta.title}`; + let name = `${ruleMeta.title}(PHP)`; + fileSites.push({key, name, ext}); + + fileSites.forEach((fileSite) => { + const site = { + key: fileSite.key, + name: fileSite.name, + type: api_type, + api, + ...ruleMeta, + ext: fileSite.ext || "", + }; + sites.push(site); + }); + }, + param: {file, phpDir, requestHost, pwd, SitesMap}, + id: file, + }; + }); + await batchExecute(php_tasks, listener); + } + const enable_cat = ENV.get('enable_cat', '1'); // 根据用户是否启用cat源去生成对应配置 if (enable_cat === '1' || enable_cat === '2') { @@ -520,6 +589,11 @@ async function generateSiteJSON(options, requestHost, sub, pwd) { filterable: 1, // 固定值 quickSearch: 1, // 固定值 }; + if (baseName.includes('[画]')) { + ruleObject.类型 = '漫画' + } else if (baseName.includes('[书]')) { + ruleObject.类型 = '小说' + } let ruleMeta = {...ruleObject}; const filePath = path.join(catDir, file); const header = await FileHeaderManager.readHeader(filePath); @@ -660,78 +734,82 @@ async function generateSiteJSON(options, requestHost, sub, pwd) { * @returns {Promise} 包含parses数组的对象 */ async function generateParseJSON(jxDir, requestHost) { - const files = readdirSync(jxDir); - const jx_files = files.filter((file) => file.endsWith('.js') && !file.startsWith('_')) // 筛选出不是 "_" 开头的 .js 文件 - const jx_dict = getParsesDict(requestHost); + let enable_self_jx = ENV.get('enable_self_jx', '0') === '1'; let parses = []; - const tasks = jx_files.map((file) => { - return { - func: async ({file, jxDir, requestHost, drpyS}) => { - const baseName = path.basename(file, '.js'); // 去掉文件扩展名 - const api = `${requestHost}/parse/${baseName}?url=`; // 使用请求的 host 地址,避免硬编码端口 - - let jxObject = { - type: 1, // 固定值 - ext: { - flag: [ - "qiyi", - "imgo", - "爱奇艺", - "奇艺", - "qq", - "qq 预告及花絮", - "腾讯", - "youku", - "优酷", - "pptv", - "PPTV", - "letv", - "乐视", - "leshi", - "mgtv", - "芒果", - "sohu", - "xigua", - "fun", - "风行" - ] - }, - header: { - "User-Agent": "Mozilla/5.0" + let sorted_parses = []; + const jx_dict = getParsesDict(requestHost); + if (enable_self_jx) { + const files = readdirSync(jxDir); + const jx_files = files.filter((file) => file.endsWith('.js') && !file.startsWith('_')) // 筛选出不是 "_" 开头的 .js 文件 + const tasks = jx_files.map((file) => { + return { + func: async ({file, jxDir, requestHost, drpyS}) => { + const baseName = path.basename(file, '.js'); // 去掉文件扩展名 + const api = `${requestHost}/parse/${baseName}?url=`; // 使用请求的 host 地址,避免硬编码端口 + + let jxObject = { + type: 1, // 固定值 + ext: { + flag: [ + "qiyi", + "imgo", + "爱奇艺", + "奇艺", + "qq", + "qq 预告及花絮", + "腾讯", + "youku", + "优酷", + "pptv", + "PPTV", + "letv", + "乐视", + "leshi", + "mgtv", + "芒果", + "sohu", + "xigua", + "fun", + "风行" + ] + }, + header: { + "User-Agent": "Mozilla/5.0" + } + }; + try { + let _jxObject = await drpyS.getJx(path.join(jxDir, file)); + jxObject = {...jxObject, ..._jxObject}; + } catch (e) { + throw new Error(`Error parsing jx object for file: ${file}, ${e.message}`); } - }; - try { - let _jxObject = await drpyS.getJx(path.join(jxDir, file)); - jxObject = {...jxObject, ..._jxObject}; - } catch (e) { - throw new Error(`Error parsing jx object for file: ${file}, ${e.message}`); - } - parses.push({ - name: baseName, - url: jxObject.url || api, - type: jxObject.type, - ext: jxObject.ext, - header: jxObject.header - }); + parses.push({ + name: baseName, + url: jxObject.url || api, + type: jxObject.type, + ext: jxObject.ext, + header: jxObject.header + }); + }, + param: {file, jxDir, requestHost, drpyS}, + id: file, + }; + }); + + const listener = { + func: (param, id, error, result) => { + if (error) { + console.error(`Error processing file ${id}:`, error.message); + } else { + // console.log(`Successfully processed file ${id}:`, result); + } }, - param: {file, jxDir, requestHost, drpyS}, - id: file, + param: {}, // 外部参数可以在这里传入 }; - }); - - const listener = { - func: (param, id, error, result) => { - if (error) { - console.error(`Error processing file ${id}:`, error.message); - } else { - // console.log(`Successfully processed file ${id}:`, result); - } - }, - param: {}, // 外部参数可以在这里传入 - }; - await batchExecute(tasks, listener); - let sorted_parses = naturalSort(parses, 'name', ['JSON并发', 'JSON合集', '虾米', '奇奇']); + await batchExecute(tasks, listener); + sorted_parses = naturalSort(parses, 'name', ['JSON并发', 'JSON合集', '虾米', '奇奇']); + } let sorted_jx_dict = naturalSort(jx_dict, 'name', ['J', 'W']); parses = sorted_parses.concat(sorted_jx_dict); return {parses}; diff --git a/controllers/fastlogger.js b/controllers/fastlogger.js index 474f4475..352d851d 100644 --- a/controllers/fastlogger.js +++ b/controllers/fastlogger.js @@ -11,9 +11,9 @@ dotenv.config(); const LOG_WITH_FILE = Number(process.env.LOG_WITH_FILE) || 0; const LOG_LEVEL = process.env.LOG_LEVEL && ['trace', 'debug', 'info', 'warn', 'error', 'fatal'].includes(process.env.LOG_LEVEL) ? process.env.LOG_LEVEL : 'info'; const COOKIE_AUTH_CODE = process.env.COOKIE_AUTH_CODE || 'drpys'; -console.log('LOG_WITH_FILE:', LOG_WITH_FILE); -console.log('LOG_LEVEL:', LOG_LEVEL); -console.log('COOKIE_AUTH_CODE:', COOKIE_AUTH_CODE); +// console.log('LOG_WITH_FILE:', LOG_WITH_FILE); +// console.log('LOG_LEVEL:', LOG_LEVEL); +// console.log('COOKIE_AUTH_CODE:', COOKIE_AUTH_CODE); let _logger = true; let logStream = null; diff --git a/controllers/index.js b/controllers/index.js index afe461b6..aa6f2853 100644 --- a/controllers/index.js +++ b/controllers/index.js @@ -1,120 +1,59 @@ -/** - * 控制器路由注册模块 - * 统一管理和注册所有控制器路由 - * 提供应用程序的所有API端点和功能模块 - */ import formBody from '@fastify/formbody'; import websocket from '@fastify/websocket'; -// WebSocket实时日志控制器-最早引入才能全局拦截console日志 import websocketController from './websocket.js'; -// 静态文件服务控制器 import staticController from './static.js'; -// 文档服务控制器 import docsController from './docs.js'; -// 配置管理控制器 import configController from './config.js'; -// API接口控制器 import apiController from './api.js'; -// 媒体代理控制器 import mediaProxyController from './mediaProxy.js'; -// 根路径控制器 import rootController from './root.js'; -// 编码器控制器 import encoderController from './encoder.js'; -// 解码器控制器 import decoderController from './decoder.js'; -// 认证编码控制器 -import authCoderController from './authcoder.js'; -// Web界面控制器 +import authcoderController from './authcoder.js'; import webController from './web.js'; -// HTTP请求控制器 import httpController from './http.js'; -// 剪贴板推送控制器 import clipboardPusherController from './clipboard-pusher.js'; -// 任务控制器(已注释) -// import taskController from './tasker.js'; -// 定时任务控制器 +// import taskerController from './tasker.js'; import cronTaskerController from './cron-tasker.js'; -// 源检查控制器 import sourceCheckerController from './source-checker.js'; -// 图片存储控制器 import imageStoreController from './image-store.js'; -// WebDAV 代理控制器 import webdavProxyController from './webdav-proxy.js'; -// FTP 代理控制器 import ftpProxyController from './ftp-proxy.js'; -// 文件代理控制器 import fileProxyController from './file-proxy.js'; import m3u8ProxyController from './m3u8-proxy.js'; import unifiedProxyController from './unified-proxy.js'; -// WebSocket实时弹幕日志控制器 -import websocketServerController from "./websocketServer.js"; import githubController from './github.js'; +import websocketServerController from "./websocketServer.js"; -/** - * 注册所有路由控制器 - * 将各个功能模块的路由注册到Fastify实例中 - * @param {Object} fastify - Fastify应用实例 - * @param {Object} options - 路由配置选项 - */ export const registerRoutes = (fastify, options) => { - // 注册插件以支持 application/x-www-form-urlencoded fastify.register(formBody); - // 注册WebSocket插件 fastify.register(websocket); - // 注册WebSocket路由 + fastify.register(websocketController, options); - // 注册静态文件服务路由 fastify.register(staticController, options); - // 注册文档服务路由 fastify.register(docsController, options); - // 注册配置管理路由 fastify.register(configController, options); - // 注册API接口路由 fastify.register(apiController, options); - // 注册媒体代理路由 fastify.register(mediaProxyController, options); - // 注册根路径路由 fastify.register(rootController, options); - // 注册编码器路由 fastify.register(encoderController, options); - // 注册解码器路由 fastify.register(decoderController, options); - // 注册认证编码路由 - fastify.register(authCoderController, options); - // 注册Web界面路由 + fastify.register(authcoderController, options); fastify.register(webController, options); - // 注册HTTP请求路由 fastify.register(httpController, options); - // 注册剪贴板推送路由 fastify.register(clipboardPusherController, options); - // 注册任务路由(已注释) - // fastify.register(taskController, options); - // 注册定时任务路由 + // fastify.register(taskerController, options); fastify.register(cronTaskerController, options); - // 注册源检查路由 fastify.register(sourceCheckerController, options); - // 注册图片存储路由 fastify.register(imageStoreController, options); - // 注册 WebDAV 代理路由 fastify.register(webdavProxyController, options); - // 注册 FTP 代理路由 fastify.register(ftpProxyController, options); - // 注册文件代理路由 fastify.register(fileProxyController, options); fastify.register(m3u8ProxyController, options); - // 注册统一代理路由 fastify.register(unifiedProxyController, options); - // 注册GitHub Release路由 fastify.register(githubController, options); }; -/** - * 注册弹幕路由控制器 - * 将弹幕功能模块的路由注册到Fastify实例中 - * @param {Object} wsApp - Ws实时弹幕预览应用实例 - * @param {Object} options - 路由配置选项 - */ export const registerWsRoutes = (wsApp, options) => { wsApp.register(websocketServerController, options); -} \ No newline at end of file +}; diff --git a/controllers/static.js b/controllers/static.js index 18ccb24e..66493679 100644 --- a/controllers/static.js +++ b/controllers/static.js @@ -79,6 +79,19 @@ export default (fastify, options, done) => { } }); + // 注册PHP脚本文件服务 - 用于存放PHP相关的脚本文件 + fastify.register(fastifyStatic, { + root: options.phpDir, // PHP脚本根目录 + prefix: '/php/', // URL访问前缀 + decorateReply: false, // 禁用 sendFile 装饰器 + setHeaders: (res, path) => { + // 为PHP文件设置正确的Content-Type,确保浏览器以纯文本形式显示 + if (path.endsWith('.php')) { + res.setHeader('Content-Type', 'text/plain; charset=utf-8') + } + } + }); + // 注册CAT相关文件服务 - 用于存放CAT视频源相关文件 fastify.register(fastifyStatic, { root: options.catDir, // CAT文件根目录 diff --git a/controllers/web.js b/controllers/web.js index ce58537a..516cffb0 100644 --- a/controllers/web.js +++ b/controllers/web.js @@ -1,9 +1,93 @@ -import {readFileSync, existsSync} from 'fs'; +import {readFileSync, existsSync, readdirSync, statSync, unlinkSync, mkdirSync, copyFileSync, lstatSync, writeFileSync} from 'fs'; +import {createReadStream} from 'fs'; +import {execSync} from 'child_process'; import path from 'path'; +import {fileURLToPath} from 'url'; +import {createHash} from 'crypto'; import {ENV} from '../utils/env.js'; import COOKIE from '../utils/cookieManager.js'; +import {validateBasicAuth} from '../utils/api_validate.js'; const COOKIE_AUTH_CODE = process.env.COOKIE_AUTH_CODE || 'drpys'; +const IS_VERCEL = process.env.VERCEL; +const DOWNLOAD_AUTH_SECRET = process.env.DOWNLOAD_AUTH_SECRET || 'drpys_download_secret'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const projectRootDir = path.dirname(__dirname); +const pkg = JSON.parse(readFileSync(path.join(projectRootDir, 'package.json'), 'utf-8')); + +const generateDownloadToken = (filename) => { + const timestamp = Date.now(); + const data = `${filename}-${timestamp}-${DOWNLOAD_AUTH_SECRET}`; + const token = createHash('md5').update(data).digest('hex'); + return `${token}-${timestamp}`; +}; + +const validateDownloadToken = (filename, token) => { + if (!token) return false; + const parts = token.split('-'); + if (parts.length < 2) return false; + const timestamp = parseInt(parts.pop()); + const hash = parts.join('-'); + const data = `${filename}-${timestamp}-${DOWNLOAD_AUTH_SECRET}`; + const expectedHash = createHash('md5').update(data).digest('hex'); + const now = Date.now(); + return hash === expectedHash && (now - timestamp) < 3600000; +}; + +const findLatestPackage = (projectDir, packageName) => { + try { + const parentDir = path.dirname(projectDir); + const files = readdirSync(parentDir); + + const isGreen = packageName.includes('-green'); + const ext = packageName.split('.').pop(); + const baseName = packageName.replace(/-green\.[^.]+$/, '').replace(/\.[^.]+$/, ''); + const pattern = new RegExp(`^${baseName.replace(/\./g, '\\.')}-\\d{8}${isGreen ? '-green' : ''}\\.${ext}`); + + console.log(`查找包: ${packageName}, 正则: ${pattern.source}, 父目录: ${parentDir}`); + console.log('目录中的文件:', files.filter(f => f.includes('drpy-node'))); + + const packageFiles = files + .filter(file => pattern.test(file)) + .map(file => { + const filePath = path.join(parentDir, file); + const stats = statSync(filePath); + return {file, filePath, mtime: stats.mtime, size: stats.size}; + }) + .sort((a, b) => b.mtime - a.mtime); + + console.log('匹配到的文件:', packageFiles.map(f => f.file)); + return packageFiles.length > 0 ? packageFiles[0] : null; + } catch (error) { + console.error('查找包失败:', error.message); + return null; + } +}; + +const buildPackage = (packageName) => { + try { + let command = 'node package.js'; + if (packageName.includes('-green')) { + command += ' -g'; + } + if (packageName.includes('.zip')) { + command += ' -z'; + } + + console.log(`执行打包命令: ${command}, 目录: ${projectRootDir}`); + const output = execSync(command, {cwd: projectRootDir, stdio: 'pipe'}); + console.log('打包输出:', output.toString()); + const result = findLatestPackage(projectRootDir, packageName); + console.log('打包后查找结果:', result ? result.file : '未找到'); + return result; + } catch (error) { + console.error('打包失败:', error.message); + console.error('错误详情:', error.stdout?.toString(), error.stderr?.toString()); + throw error; + } +}; export default (fastify, options, done) => { fastify.get('/admin/encoder', async (request, reply) => { @@ -75,5 +159,375 @@ export default (fastify, options, done) => { } }); + fastify.get('/admin/download', { + preHandler: validateBasicAuth + }, async (request, reply) => { + try { + if (IS_VERCEL) { + return reply.code(403).send({ + success: false, + message: 'Vercel 环境不支持文件下载功能', + }); + } + + const projectName = path.basename(projectRootDir); + const templatePath = path.join(projectRootDir, 'public', 'download.html'); + + if (!existsSync(templatePath)) { + return reply.code(500).send({ + success: false, + message: '下载页面模板不存在', + }); + } + + let html = readFileSync(templatePath, 'utf-8'); + + const files = [ + {name: `${projectName}.7z`, desc: '7z 压缩包(标准版)'}, + {name: `${projectName}.zip`, desc: 'ZIP 压缩包(标准版)'}, + {name: `${projectName}-green.7z`, desc: '7z 压缩包(绿色版,不含[密]文件)'}, + {name: `${projectName}-green.zip`, desc: 'ZIP 压缩包(绿色版,不含[密]文件)'} + ]; + + const formatFileSize = (bytes) => { + if (!bytes || bytes === 0) return '未打包'; + const mb = bytes / (1024 * 1024); + return mb.toFixed(2) + ' MB'; + }; + + const downloadItems = files.map(file => { + const latestPackage = findLatestPackage(projectRootDir, file.name); + const fileSize = latestPackage ? formatFileSize(latestPackage.size) : '未打包'; + const sizeClass = latestPackage ? '' : ' not-packed'; + const token = generateDownloadToken(file.name); + const downloadUrl = `/admin/download/${file.name}?auth=${token}`; + + let buildTime = '未打包'; + if (latestPackage && latestPackage.mtime) { + const date = new Date(latestPackage.mtime); + buildTime = date.toLocaleString('zh-CN', { hour12: false }); + } + + return '
' + + '
' + + '
' + file.name + '
' + + '
' + file.desc + '
' + + '
版本: ' + pkg.version + ' | 打包时间: ' + buildTime + '
' + + '
' + + '
' + fileSize + '
' + + '
' + + '下载' + + '' + + '
' + + '
'; + }).join(''); + + html = html.replace(/\{\{projectName\}\}/g, projectName); + html = html.replace(/\{\{downloadItems\}\}/g, downloadItems); + + reply.type('text/html').send(html); + } catch (error) { + console.error('获取下载页面失败:', error.message); + return reply.code(500).send({ + success: false, + message: '获取下载页面失败', + error: error.message, + }); + } + }); + + fastify.get('/admin/download/:filename', { + preHandler: async (request, reply) => { + const {auth} = request.query; + if (validateDownloadToken(request.params.filename, auth)) { + return; + } + const authHeader = request.headers.authorization; + if (!authHeader) { + reply.header('WWW-Authenticate', 'Basic'); + return reply.code(401).send('Authentication required'); + } + const base64Credentials = authHeader.split(' ')[1]; + const credentials = Buffer.from(base64Credentials, 'base64').toString('utf-8'); + const [username, password] = credentials.split(':'); + const validUsername = process.env.API_AUTH_NAME || ''; + const validPassword = process.env.API_AUTH_CODE || ''; + if (username === validUsername && password === validPassword) { + return; + } + reply.header('WWW-Authenticate', 'Basic'); + return reply.code(401).send('Invalid credentials'); + } + }, async (request, reply) => { + try { + if (IS_VERCEL) { + return reply.code(403).send({ + success: false, + message: 'Vercel 环境不支持文件下载功能', + }); + } + + const {filename} = request.params; + const projectName = path.basename(projectRootDir); + + const validFilenames = [ + `${projectName}.7z`, + `${projectName}.zip`, + `${projectName}-green.7z`, + `${projectName}-green.zip` + ]; + + if (!validFilenames.includes(filename)) { + return reply.code(400).send({ + success: false, + message: '无效的文件名', + }); + } + + let latestPackage = findLatestPackage(projectRootDir, filename); + + if (!latestPackage) { + console.log(`未找到 ${filename},开始打包...`); + latestPackage = buildPackage(filename); + if (!latestPackage) { + return reply.code(500).send({ + success: false, + message: '打包失败,无法创建压缩文件', + }); + } + } + + const fileStream = createReadStream(latestPackage.filePath); + const contentType = filename.endsWith('.zip') ? 'application/zip' : 'application/x-7z-compressed'; + reply.header('Content-Type', contentType); + reply.header('Content-Disposition', `attachment; filename="${encodeURIComponent(latestPackage.file)}"`); + return reply.send(fileStream); + } catch (error) { + console.error('下载文件失败:', error.message); + return reply.code(500).send({ + success: false, + message: '下载失败', + error: error.message, + }); + } + }); + + fastify.post('/admin/download/clear', { + preHandler: validateBasicAuth + }, async (request, reply) => { + try { + if (IS_VERCEL) { + return reply.code(403).send({ + success: false, + message: 'Vercel 环境不支持文件操作', + }); + } + + const parentDir = path.dirname(projectRootDir); + const projectName = path.basename(projectRootDir); + const files = readdirSync(parentDir); + const pattern = new RegExp(`^${projectName.replace(/\./g, '\\.')}-\\d{8}(-green)?\\.(7z|zip)$`); + + let deletedCount = 0; + const deletedFiles = []; + + for (const file of files) { + if (pattern.test(file)) { + const filePath = path.join(parentDir, file); + try { + unlinkSync(filePath); + deletedFiles.push(file); + deletedCount++; + } catch (error) { + console.error(`删除文件失败: ${file}`, error.message); + } + } + } + + return reply.send({ + success: true, + count: deletedCount, + deletedFiles, + message: `已清除 ${deletedCount} 个历史文件` + }); + } catch (error) { + console.error('清除历史文件失败:', error.message); + return reply.code(500).send({ + success: false, + message: '清除历史文件失败', + error: error.message, + }); + } + }); + + const BACKUP_PATHS = [ + '.env', + '.plugins.js', + 'config/env.json', + 'config/map.txt', + 'config/parses.conf', + 'config/player.json', + 'scripts/cron', + 'plugins' + ]; + + const BACKINFO_FILENAME = '.backinfo'; + + const getBackupRootDir = () => { + return path.join(path.dirname(projectRootDir), path.basename(projectRootDir) + '-backup'); + }; + + const getBackinfoPath = (backupDir) => { + return path.join(backupDir, BACKINFO_FILENAME); + }; + + const loadBackinfo = (backupDir) => { + const infoPath = getBackinfoPath(backupDir); + if (!existsSync(infoPath)) { + return null; + } + try { + const content = readFileSync(infoPath, 'utf-8'); + return JSON.parse(content); + } catch (e) { + return null; + } + }; + + const saveBackinfo = (backupDir, data) => { + const infoPath = getBackinfoPath(backupDir); + writeFileSync(infoPath, JSON.stringify(data, null, 2), 'utf-8'); + }; + + const getEffectiveBackupPaths = (backupDir) => { + const info = loadBackinfo(backupDir); + if (info && Array.isArray(info.paths) && info.paths.length > 0) { + return {paths: info.paths, info}; + } + return {paths: BACKUP_PATHS, info}; + }; + + const copyRecursiveSync = (src, dest) => { + const stats = lstatSync(src); + if (stats.isDirectory()) { + if (!existsSync(dest)) { + mkdirSync(dest, { recursive: true }); + } + readdirSync(src).forEach((childItemName) => { + copyRecursiveSync(path.join(src, childItemName), path.join(dest, childItemName)); + }); + } else { + const destDir = path.dirname(dest); + if (!existsSync(destDir)) { + mkdirSync(destDir, { recursive: true }); + } + copyFileSync(src, dest); + } + }; + + fastify.get('/admin/backup/config', { + preHandler: validateBasicAuth + }, async (request, reply) => { + const backupDir = getBackupRootDir(); + let paths; + let lastBackupAt = null; + let lastRestoreAt = null; + if (!existsSync(backupDir)) { + paths = BACKUP_PATHS; + } else { + const result = getEffectiveBackupPaths(backupDir); + paths = result.paths; + if (result.info) { + lastBackupAt = result.info.lastBackupAt || null; + lastRestoreAt = result.info.lastRestoreAt || null; + } + } + return reply.send({success: true, paths, lastBackupAt, lastRestoreAt}); + }); + + fastify.post('/admin/backup', { + preHandler: validateBasicAuth + }, async (request, reply) => { + if (IS_VERCEL) { + return reply.code(403).send({ success: false, message: 'Vercel环境不支持备份' }); + } + try { + const backupDir = getBackupRootDir(); + if (!existsSync(backupDir)) { + mkdirSync(backupDir, { recursive: true }); + } + + const {paths, info} = getEffectiveBackupPaths(backupDir); + const details = []; + for (const item of paths) { + const srcPath = path.join(projectRootDir, item); + const destPath = path.join(backupDir, item); + + if (existsSync(srcPath)) { + copyRecursiveSync(srcPath, destPath); + details.push(`Backed up: ${item}`); + } else { + details.push(`Skipped (not found): ${item}`); + } + } + + const now = new Date().toISOString(); + const customPaths = info && Array.isArray(info.paths) && info.paths.length > 0 ? info.paths : []; + const backinfoData = { + paths: customPaths, + lastBackupAt: now, + lastRestoreAt: info && info.lastRestoreAt ? info.lastRestoreAt : null + }; + saveBackinfo(backupDir, backinfoData); + + return reply.send({ success: true, message: '备份完成', backupDir, details }); + } catch (error) { + fastify.log.error(`Backup failed: ${error.message}`); + return reply.code(500).send({ success: false, message: '备份失败: ' + error.message }); + } + }); + + fastify.post('/admin/restore', { + preHandler: validateBasicAuth + }, async (request, reply) => { + if (IS_VERCEL) { + return reply.code(403).send({ success: false, message: 'Vercel环境不支持恢复' }); + } + try { + const backupDir = getBackupRootDir(); + if (!existsSync(backupDir)) { + return reply.code(404).send({ success: false, message: '备份目录不存在' }); + } + + const {paths, info} = getEffectiveBackupPaths(backupDir); + const details = []; + for (const item of paths) { + const srcPath = path.join(backupDir, item); + const destPath = path.join(projectRootDir, item); + + if (existsSync(srcPath)) { + copyRecursiveSync(srcPath, destPath); + details.push(`Restored: ${item}`); + } else { + details.push(`Skipped (not found in backup): ${item}`); + } + } + + const now = new Date().toISOString(); + const customPaths = info && Array.isArray(info.paths) && info.paths.length > 0 ? info.paths : []; + const backinfoData = { + paths: customPaths, + lastBackupAt: info && info.lastBackupAt ? info.lastBackupAt : null, + lastRestoreAt: now + }; + saveBackinfo(backupDir, backinfoData); + + return reply.send({ success: true, message: '恢复完成', backupDir, details }); + } catch (error) { + fastify.log.error(`Restore failed: ${error.message}`); + return reply.code(500).send({ success: false, message: '恢复失败: ' + error.message }); + } + }); + done(); }; diff --git a/docs/envdoc.md b/docs/envdoc.md index 8d4ea81c..f50c7c8a 100644 --- a/docs/envdoc.md +++ b/docs/envdoc.md @@ -26,6 +26,7 @@ | VIRTUAL_ENV | 本地python虚拟环境路径 | 同上,差别在于虚拟环境会自动拼scripts路径下的python.exe,跟真实环境二选一 | | daemonMode | 守护进程版本 | 0: 旗舰版 1:轻量版 | | DS_REQ_LIB | ds/cat 默认req实现 | 0:fetch 1:axios (已知模式1为前面版本默认功能,但是后面发现某些场景无法获取源码,新写了模式0,不保证完全兼容) | +| PHP_PATH | 本地PHP可执行文件路径 | php (全局) 或 /usr/bin/php8.3 (指定路径) | | CLIPBOARD_MAX_SIZE | 单次文本传输最大体积 默认100KB | 102400 | | CLIPBOARD_SECURITY_CODE | 剪切板接口请求头安全码 | drpys | | CLIPBOARD_ALLOWED_CHARSET | 允许字符集,默认utf-8 | utf-8 | @@ -35,3 +36,65 @@ | MAX_TEXT_SIZE | 设置最大文本大小(剪切板插件) | 0.1 * 1024 * 1024 | | MAX_IMAGE_SIZE | 设置最大图片大小(图片插件) | 0.5 * 1024 * 1024 | +# 用户自定义配置 (config/env.json) + +该文件位于 `config/env.json`,存储用户自定义的运行时配置。 + +| 参数键 | 参数说明 | 备注 | +| :--- | :--- | :--- | +| enable_php | 是否开启 PHP 源支持 | 0:关闭 1:开启(本地执行T4,需环境) 2:开启(远程加载T3,免环境) | +| api_pwd | 全局接口访问密码 | 访问敏感接口或文件时需要 | +| thread | 爬虫并发数 | 建议设置在 4-8 之间 | +| quark_cookie | 夸克网盘 Cookie | 观看夸克网盘资源需要 | +| uc_cookie | UC 网盘 Cookie | 观看 UC 网盘资源需要 | +| ali_token | 阿里云盘 Token | 观看阿里云盘资源需要 | +| deepseek_apiKey | DeepSeek API Key | AI 搜索/对话功能需要 | +| kimi_apiKey | Kimi API Key | AI 搜索/对话功能需要 | +| bili_cookie | Bilibili Cookie | B站相关资源需要 | +| play_proxy_mode | 播放代理模式 | 0:直接播放 1:代理播放 | + +## 环境搭建指南 + +### 1. PHP 环境搭建 (推荐) + +本项目支持 PHP 爬虫源(`spider/php/*.php`),需要本地安装 PHP 环境。 + +#### Linux (Ubuntu/Debian) + +推荐使用 PPA 安装 PHP 8.3+: + +```bash +# 1. 添加 PPA 源 +sudo apt install software-properties-common -y +sudo add-apt-repository ppa:ondrej/php -y +sudo apt update + +# 2. 安装 PHP 8.3 及常用扩展 (drpy 爬虫需要 curl, mbstring, xml, mysql 等) +sudo apt install php8.3-cli php8.3-curl php8.3-mbstring php8.3-xml php8.3-mysql -y + +# 3. 验证安装 +php -v +``` + +#### Windows + +1. 下载 PHP 8.3+ NTS 版本 (推荐)。 +2. 解压到 `C:\php` 等目录。 +3. 将解压目录添加到系统 `Path` 环境变量中。 +4. 修改 `php.ini`,开启 `extension=curl`, `mbstring`, `openssl` 等扩展。 + +### 2. 7-Zip 工具安装 (可选) + +部分功能可能依赖 7z 进行解压操作。 + +#### Linux (Ubuntu/Debian) + +```bash +sudo apt update +sudo apt install p7zip-full -y +``` + +验证安装: +```bash +7z +``` diff --git a/docs/updateRecord.md b/docs/updateRecord.md index 4a487152..6fa7355e 100644 --- a/docs/updateRecord.md +++ b/docs/updateRecord.md @@ -1,5 +1,32 @@ # drpyS更新记录 +### 20260131 + +更新至V1.3.21 + +1. 更新一点文档和文件名称 +2. 修复番茄动漫ds源在皮卡丘壳子上无法使用问题( + BUG羊的壳子tid为链接时处理逻辑一团乱,http链接被篡改成https就算了,链接含有{{page}}变量竟然被篡改成1了) +3. 更新文档、生成配置类型,使php、py源也更兼容皮卡丘的漫画小说 + +### 20260127 + +更新至V1.3.20 + +重磅升级来了!!! + +1. 支持了php适配器,支持自动加载php源,环境变量新增 `PHP_PATH=`,如果不指定默认则是'php',可以配置成自己的路径 +2. 尝试启动加速,插件异步加载 +3. 启动日志大幅精简,还你一个干净清爽的启动界面 +4. 设置中心修改,支持启用/关闭 PHP的源 + +### 20260125 + +更新至V1.3.19 + +1. 合并了E佬修复&新增的源 +2. 增加了PHP的T4源标准 + ### 20260118 更新至V1.3.18 diff --git a/index.js b/index.js index 2c27bfac..f31b67ae 100644 --- a/index.js +++ b/index.js @@ -1,5 +1,9 @@ +import { performance } from 'perf_hooks'; +const startTime = performance.now(); + import * as fastlogger from './controllers/fastlogger.js' import path from 'path'; +import {checkPhpAvailable} from './utils/phpEnv.js'; import os from 'os'; import qs from 'qs'; import {fileURLToPath} from 'url'; @@ -30,16 +34,23 @@ const jsonDir = path.join(__dirname, 'json'); const jsDir = path.join(__dirname, 'spider/js'); const dr2Dir = path.join(__dirname, 'spider/js_dr2'); const pyDir = path.join(__dirname, 'spider/py'); +const phpDir = path.join(__dirname, 'spider/php'); const catDir = path.join(__dirname, 'spider/catvod'); const catLibDir = path.join(__dirname, 'spider/catLib'); const xbpqDir = path.join(__dirname, 'spider/xbpq'); const configDir = path.join(__dirname, 'config'); -const pluginProcs = startAllPlugins(__dirname); -// console.log('pluginProcs:', pluginProcs); +// 异步启动插件,不阻塞主线程 +let pluginProcs = {}; +setTimeout(() => { + pluginProcs = startAllPlugins(__dirname); +}, 0); // 添加钩子事件 fastify.addHook('onReady', async () => { + await checkPhpAvailable(); + const endTime = performance.now(); + console.log(`🚀 Server started in ${(endTime - startTime).toFixed(2)}ms`); try { await daemon.startDaemon(); fastify.log.info('Python守护进程已启动'); @@ -168,6 +179,7 @@ const registerOptions = { jsDir, dr2Dir, pyDir, + phpDir, catDir, catLibDir, xbpqDir, diff --git "a/json/App\346\250\241\346\235\277\351\205\215\347\275\256.json" "b/json/App\346\250\241\346\235\277\351\205\215\347\275\256.json" index c78b8a10..eb9a2801 100644 --- "a/json/App\346\250\241\346\235\277\351\205\215\347\275\256.json" +++ "b/json/App\346\250\241\346\235\277\351\205\215\347\275\256.json" @@ -26,18 +26,10 @@ }, "仓鼠": { "muban": "AppQiji", - "host": "", - "hosturl": "https://ceshi307386.oss-cn-beijing.aliyuncs.com/ceshi421.txt", - "key": "da61247f5b662597", - "iv": "da61247f5b662597", - "verify": "true" - }, - "紫金": { - "muban": "AppGet", - "host": "", - "hosturl": "https://snysw.xyz/mf4kzs327.txt", - "key": "1234567887654321", - "iv": "1234567887654321", + "host": "https://hk440cms.cs4k.top", + "hosturl": "", + "key": "fL7sY4zN4kB3pG4p", + "iv": "fL7sY4zN4kB3pG4p", "verify": "true" }, "云云": { @@ -88,6 +80,30 @@ "iv": "sada21321sdq231d", "verify": "true" }, + "优兔": { + "muban": "AppQiji", + "host": "", + "hosturl": "https://uututv-1319209748.cos.ap-shanghai.myqcloud.com/uutuv4.txt", + "key": "UrWKPnmQWJA8AQzd", + "iv": "UrWKPnmQWJA8AQzd", + "verify": "true" + }, + "王子": { + "muban": "AppGet", + "host": "https://app.95112475.xyz", + "hosturl": "", + "key": "5a9w6x58dsq6z3a6", + "iv": "5a9w6x58dsq6z3a6", + "verify": "true" + }, + "紫金": { + "muban": "AppGet", + "host": "", + "hosturl": "https://snysw.xyz/mf4kzs327.txt", + "key": "1234567887654321", + "iv": "1234567887654321", + "verify": "true" + }, "数字": { "muban": "AppGet", "host": "http://app1-0-0.87333.cc", @@ -162,8 +178,8 @@ }, "顾我": { "muban": "AppQiji", - "host": "", - "hosturl": "https://guwozj-1319364746.cos.ap-guangzhou.myqcloud.com/guwo.txt", + "host": "http://117.50.204.35:520", + "hosturl": "", "key": "ca94b06ca3c7d80e", "iv": "ca94b06ca3c7d80e", "verify": "true" @@ -184,6 +200,14 @@ "iv": "qkxnwkfjwpcnwycl", "verify": "true" }, + "番薯动漫": { + "muban": "AppGet", + "host": "https://new.app.bytegooty.com", + "hosturl": "", + "key": "N4yj7l7xKxHF4*gz", + "iv": "N4yj7l7xKxHF4*gz", + "verify": "true" + }, "咕咕动漫": { "muban": "AppGet", "host": "https://www.gugu3.com", diff --git a/libs/catvod.js b/libs/catvod.js index efb465cf..320d850d 100644 --- a/libs/catvod.js +++ b/libs/catvod.js @@ -14,7 +14,7 @@ const _config_path = path.join(__dirname, '../config'); const _lib_path = path.join(__dirname, '../spider/catvod'); const enable_cat_debug = Number(process.env.CAT_DEBUG) !== 2; -console.log('enable_cat_debug:', enable_cat_debug); +// console.log('enable_cat_debug:', enable_cat_debug); const json2Object = function (json) { if (!json) { @@ -184,10 +184,16 @@ const category = async function (filePath, env, tid, pg = 1, filter = 1, extend const detail = async function (filePath, env, ids) { const moduleObject = await init(filePath, env); const vod_id = Array.isArray(ids) ? ids[0] : ids; - return json2Object(await moduleObject.detail(vod_id)); + let detailResult = '{}'; + // console.log('type of detailContent:', typeof moduleObject.detailContent); + if (moduleObject.detailContent) { // tvbox形式猫源二级参数传ids列表 + detailResult = await moduleObject.detailContent(ids); + } else { // ds/cat传非id + detailResult = await moduleObject.detail(vod_id); + } + return json2Object(detailResult); } - const search = async function (filePath, env, wd, quick = 0, pg = 1) { const moduleObject = await init(filePath, env); return json2Object(await moduleObject.search(wd, quick, pg)); diff --git a/libs/php.js b/libs/php.js new file mode 100644 index 00000000..4e582268 --- /dev/null +++ b/libs/php.js @@ -0,0 +1,220 @@ +import path from "path"; +import {readFile} from "fs/promises"; +import {fileURLToPath} from 'url'; +import {execFile} from 'child_process'; +import {promisify} from 'util'; +import {getSitesMap} from "../utils/sites-map.js"; +import {computeHash, deepCopy, getNowTime, urljoin} from "../utils/utils.js"; +import {prepareBinary} from "../utils/binHelper.js"; +import {md5} from "../libs_drpy/crypto-util.js"; +import {fastify} from "../controllers/fastlogger.js"; +// import dotenv from 'dotenv'; +// +// dotenv.config({ path: path.join(process.cwd(), '.env.development') }); + +const execFileAsync = promisify(execFile); +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const _config_path = path.join(__dirname, '../config'); +const _bridge_path = path.join(__dirname, '../spider/php/_bridge.php'); + +// Cache for module objects +const moduleCache = new Map(); + +// Mapping from JS method names to PHP Spider method names +const methodMapping = { + 'init': 'init', + 'home': 'homeContent', + 'homeVod': 'homeVideoContent', + 'category': 'categoryContent', + 'detail': 'detailContent', + 'search': 'searchContent', + 'play': 'playerContent', + 'proxy': 'proxy', // Not standard in BaseSpider, but might exist + 'action': 'action' // Not standard +}; + +// Helper to stringify args for CLI +function stringify(arg) { + if (arg === undefined) return 'null'; + return JSON.stringify(arg); +} + +// Helper to parse JSON output +function json2Object(json) { + if (!json) return {}; + if (typeof json === 'object') return json; + try { + return JSON.parse(json); + } catch (e) { + return json; + } +} + +// Execute PHP bridge +const callPhpMethod = async (filePath, methodName, env, ...args) => { + let phpPath = process.env.PHP_PATH || 'php'; + + const validPath = prepareBinary(phpPath); + if (!validPath) { + throw new Error(`PHP executable not found or invalid: ${phpPath}`); + } + phpPath = validPath; + + const phpMethodName = methodMapping[methodName] || methodName; + + const cliArgs = [ + _bridge_path, + filePath, + phpMethodName, + JSON.stringify(env), + ...args.map(stringify) + ]; + + try { + // fastify.log.info(`Calling PHP: ${phpPath} ${cliArgs.join(' ')}`); + const {stdout, stderr} = await execFileAsync(phpPath, cliArgs, { + encoding: 'utf8', + maxBuffer: 10 * 1024 * 1024, // 10MB buffer + env: { + ...process.env, + PYTHONIOENCODING: 'utf-8', // Just in case + // Add any PHP specific env vars if needed + } + }); + + if (stderr) { + // Log stderr but don't fail immediately unless stdout is empty or error + // fastify.log.warn(`PHP Stderr: ${stderr}`); + console.error(`PHP Stderr: ${stderr}`); + } + + const result = json2Object(stdout.trim()); + + if (result && result.error) { + throw new Error(`PHP Error: ${result.error}\nTrace: ${result.traceback}`); + } + + return result; + + } catch (error) { + console.error(`Error calling PHP method ${methodName}:`, error); + throw error; + } +}; + +const loadEsmWithHash = async function (filePath, fileHash, env) { + const spiderProxy = {}; + const spiderMethods = Object.keys(methodMapping); + + spiderMethods.forEach(method => { + spiderProxy[method] = async (...args) => { + return callPhpMethod(filePath, method, env, ...args); + }; + }); + + return spiderProxy; +}; + +const init = async function (filePath, env = {}, refresh) { + try { + const fileContent = await readFile(filePath, 'utf-8'); + const fileHash = computeHash(fileContent); + const moduleName = path.basename(filePath, '.php'); // .php extension + let moduleExt = env.ext || ''; + + // Logic for SitesMap and moduleExt (similar to hipy.js) + let SitesMap = getSitesMap(_config_path); + if (moduleExt && SitesMap[moduleName]) { + // ... logic for compressed ext ... + // Simplified for now, assuming plain string or handled by caller + } + + let hashMd5 = md5(filePath + '#php#' + moduleExt); + + if (moduleCache.has(hashMd5) && !refresh) { + const cached = moduleCache.get(hashMd5); + if (cached.hash === fileHash) { + return cached.moduleObject; + } + } + + fastify.log.info(`Loading PHP module: ${filePath}`); + let t1 = getNowTime(); + + const module = await loadEsmWithHash(filePath, fileHash, env); + const rule = module; + + // Initialize the spider + const initValue = await rule.init(moduleExt) || {}; + + let t2 = getNowTime(); + const moduleObject = deepCopy(rule); + moduleObject.cost = t2 - t1; + + moduleCache.set(hashMd5, {moduleObject, hash: fileHash}); + return {...moduleObject, ...initValue}; + + } catch (error) { + fastify.log.error(`Error in php.init :${filePath}`, error); + throw new Error(`Failed to initialize PHP module:${error.message}`); + } +}; + +const getRule = async function (filePath, env) { + const moduleObject = await init(filePath, env); + return JSON.stringify(moduleObject); +}; + +const home = async function (filePath, env, filter = 1) { + const moduleObject = await init(filePath, env); + return json2Object(await moduleObject.home(filter)); +}; + +const homeVod = async function (filePath, env) { + const moduleObject = await init(filePath, env); + const homeVodResult = json2Object(await moduleObject.homeVod()); + return homeVodResult && homeVodResult.list ? homeVodResult.list : homeVodResult; +}; + +const category = async function (filePath, env, tid, pg = 1, filter = 1, extend = {}) { + const moduleObject = await init(filePath, env); + return json2Object(await moduleObject.category(tid, pg, filter, extend)); +}; + +const detail = async function (filePath, env, ids) { + const moduleObject = await init(filePath, env); + return json2Object(await moduleObject.detail(ids)); +}; + +const search = async function (filePath, env, wd, quick = 0, pg = 1) { + const moduleObject = await init(filePath, env); + return json2Object(await moduleObject.search(wd, quick, pg)); +}; + +const play = async function (filePath, env, flag, id, flags) { + const moduleObject = await init(filePath, env); + return json2Object(await moduleObject.play(flag, id, flags)); +}; + +const proxy = async function (filePath, env, params) { + const moduleObject = await init(filePath, env); + return json2Object(await moduleObject.proxy(params)); +}; + +const action = async function (filePath, env, action, value) { + const moduleObject = await init(filePath, env); + return json2Object(await moduleObject.action(action, value)); +}; + +export default { + getRule, + init, + home, + homeVod, + category, + detail, + search, + play, + proxy, + action +}; diff --git a/libs_drpy/drpyInject.js b/libs_drpy/drpyInject.js index 75d25c8c..27598677 100644 --- a/libs_drpy/drpyInject.js +++ b/libs_drpy/drpyInject.js @@ -163,10 +163,10 @@ async function request(url, opt = {}) { let effectivePostType = postType; if (!effectivePostType) { // 查找不区分大小写的 Content-Type 头 - const contentTypeKey = Object.keys(headers).find(key => + const contentTypeKey = Object.keys(headers).find(key => key.toLowerCase() === 'content-type' ); - + if (contentTypeKey && headers[contentTypeKey]) { const contentType = headers[contentTypeKey].toLowerCase(); if (contentType.includes('application/x-www-form-urlencoded')) { @@ -176,7 +176,7 @@ async function request(url, opt = {}) { } } } - + // 根据有效的 postType 处理数据 if (effectivePostType === 'form' && data != null && typeof data === 'object') { data = qs.stringify(data, {encode: false}); @@ -724,4 +724,58 @@ globalThis.jsonToCookie = jsonToCookie; globalThis.cookieToJson = cookieToJson; globalThis.keysToLowerCase = keysToLowerCase; +class BaseSpider { + constructor() { + this.home = this.homeContent; + this.category = this.categoryContent; + // this.detail = this.detailContent; + this.search = this.searchContent; + this.play = this.playerContent; + this.homeVod = this.homeVideoContent; + this.proxy = this.localProxy; + // this.fetch = request; + } + + async fetch(url, options) { + const resp = await req(url, options); + return { + ...resp, + get data() { // data尝试返回object + try { + return this.content.parseX; + } catch (e) { + return {}; + } + } + }; + } + + async homeContent() { + } + + async categoryContent() { + } + + async detailContent() { + } + + async searchContent() { + } + + async playerContent() { + } + + async homeVideoContent() { + } + + async localProxy() { + + } + + async action() { + + } +} + +globalThis.BaseSpider = BaseSpider; export default {}; diff --git a/libs_drpy/fetchAxios.js b/libs_drpy/fetchAxios.js index 789fe5c2..4e90ffa8 100644 --- a/libs_drpy/fetchAxios.js +++ b/libs_drpy/fetchAxios.js @@ -4,6 +4,50 @@ */ import FormData from 'form-data'; import https from "https"; +import diagnosticsChannel from 'diagnostics_channel'; + +let undiciStripUASubscribed = false; + +function ensureUndiciStripUASubscription() { + if (undiciStripUASubscribed) { + return; + } + undiciStripUASubscribed = true; + + diagnosticsChannel.channel('undici:request:create').subscribe(({request}) => { + if (!request || !Array.isArray(request.headers)) { + return; + } + const headers = request.headers; + + let shouldStrip = false; + for (let i = 0; i < headers.length; i += 2) { + const k = headers[i]; + if (typeof k === 'string' && k.toLowerCase() === 'x-remove-user-agent') { + shouldStrip = true; + break; + } + } + if (!shouldStrip) { + return; + } + + for (let i = 0; i < headers.length; i += 2) { + const k = headers[i]; + if (typeof k === 'string' && k.toLowerCase() === 'x-remove-user-agent') { + headers.splice(i, 2); + i -= 2; + } + } + for (let i = 0; i < headers.length; i += 2) { + const k = headers[i]; + if (typeof k === 'string' && k.toLowerCase() === 'user-agent') { + headers.splice(i, 2); + i -= 2; + } + } + }); +} /** * FetchAxios类 - HTTP客户端实现 @@ -71,6 +115,18 @@ class FetchAxios { finalConfig = await interceptor(finalConfig) || finalConfig; } + if (finalConfig.headers) { + const headerKeys = Object.keys(finalConfig.headers); + for (const key of headerKeys) { + if (key.toLowerCase() === 'user-agent' && finalConfig.headers[key] === 'RemoveUserAgent') { + delete finalConfig.headers[key]; + finalConfig.headers['x-remove-user-agent'] = '1'; + ensureUndiciStripUASubscription(); + break; + } + } + } + // 拼接查询参数 if (finalConfig.params) { const query = new URLSearchParams(finalConfig.params).toString(); @@ -300,4 +356,4 @@ export function createHttpsInstance() { responseType: 'arraybuffer', httpsAgent: httpsAgent }); -} \ No newline at end of file +} diff --git a/libs_drpy/req-extend.js b/libs_drpy/req-extend.js index 86a75912..cf080579 100644 --- a/libs_drpy/req-extend.js +++ b/libs_drpy/req-extend.js @@ -40,6 +40,9 @@ async function request(url, obj = {}, ocr_flag = false) { obj.headers["Content-Type"] = 'text/html; charset=' + rule.encoding; } } + if (rule.timeout && typeof obj.timeout === 'undefined') { + obj.timeout = rule.timeout; + } if (typeof (obj.body) != 'undefined' && obj.body && typeof (obj.body) === 'string') { // 传body加 "Content-Type":"application/x-www-form-urlencoded;" 即可post form if (!obj.headers.hasOwnProperty('Content-Type') && !obj.headers.hasOwnProperty('content-type')) { // 手动指定了就不管 @@ -75,7 +78,7 @@ async function request(url, obj = {}, ocr_flag = false) { // } log(`[request] headers: ${JSON.stringify(obj.headers)}`); - log('[request] url:' + url + ` |method:${obj.method || 'GET'} |body:${obj.body || ''}`); + log('[request] url:' + url + ` |method:${obj.method || 'GET'}|timeout:${obj.timeout} |body:${obj.body || ''}`); let res = await req(url, obj); let html = res.content || ''; if (obj.withHeaders) { diff --git a/package.js b/package.js index 1e52492e..9cc52648 100644 --- a/package.js +++ b/package.js @@ -7,7 +7,7 @@ import url from 'url'; const EXCLUDE_DIRS = ['.git', '.idea', 'soft', 'examples', 'apps/cat', 'plugins/pvideo', 'plugins/req-proxy', 'plugins/pup-sniffer', 'plugins/mediaProxy', 'pyTools', 'drop_code', 'jstest', 'local', 'logs', '对话1.txt', 'vod_cache', 'data/mv']; // 要排除的文件列表 -const EXCLUDE_FILES = ['config/env.json', '.env', '.claude', 'clipboard.txt', 'clipboard.txt.bak', '.plugins.js', 'yarn.lock', 't4_daemon.pid', 'spider/js/UC分享.js', 'spider/js/百忙无果[官].js', 'json/UC分享.json', 'jx/_30wmv.js', 'jx/奇奇.js', 'jx/芒果关姐.js', 'data/settings/link_data.json', 'index.json', 'custom.json']; +const EXCLUDE_FILES = ['config/env.json', '.env', '.claude', 'clipboard.txt', 'clipboard.txt.bak', '.plugins.js', 'yarn.lock', 't4_daemon.pid', 'spider/js/UC分享.js', 'spider/js/百忙无果[官].js', 'spider/catvod/mtv60w[差].js', 'json/UC分享.json', 'jx/_30wmv.js', 'jx/奇奇.js', 'jx/芒果关姐.js', 'data/settings/link_data.json', 'index.json', 'custom.json']; // 获取脚本所在目录 const getScriptDir = () => dirname(resolve(url.fileURLToPath(import.meta.url))); @@ -37,7 +37,7 @@ const filterGreenFiles = (scriptDir) => { }; // 压缩目录 -const compressDirectory = (scriptDir, green) => { +const compressDirectory = (scriptDir, green, useZip) => { const currentDir = basename(scriptDir); const currentTime = new Date().toLocaleDateString('zh-CN', { year: 'numeric', @@ -45,12 +45,13 @@ const compressDirectory = (scriptDir, green) => { day: '2-digit' }).replace(/\//g, ''); const archiveSuffix = green ? '-green' : ''; - const archiveName = `${currentDir}-${currentTime}${archiveSuffix}.7z`; + const archiveExt = useZip ? '.zip' : '.7z'; + const archiveName = `${currentDir}-${currentTime}${archiveSuffix}${archiveExt}`; const parentDir = resolve(scriptDir, '..'); const archivePath = join(parentDir, archiveName); - // 构建 7z 命令 + // 构建压缩命令参数 const excludeParams = []; // 排除目录 @@ -77,7 +78,7 @@ const compressDirectory = (scriptDir, green) => { } // 构建命令,打包目录内容而不包含目录本身 - const command = `7z a "${archivePath}" "${join(scriptDir, '*')}" -r ${excludeParams.join(' ')}`; + const command = `7z a -t${useZip ? 'zip' : '7z'} "${archivePath}" "${join(scriptDir, '*')}" -r ${excludeParams.join(' ')}`; console.log(`构建的 7z 命令: ${command}`); try { @@ -95,8 +96,9 @@ const main = () => { // 简单解析命令行参数 const args = process.argv.slice(2); const green = args.includes('-g') || args.includes('--green'); + const useZip = args.includes('-z') || args.includes('--zip'); - compressDirectory(scriptDir, green); + compressDirectory(scriptDir, green, useZip); }; main(); diff --git a/package.json b/package.json index e350953d..0d84c6ed 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "drpy-node", - "version": "1.3.18", + "version": "1.3.21", "main": "index.js", "type": "module", "scripts": { @@ -14,8 +14,12 @@ "node22-win": "chcp 65001 && node --trace-deprecation --experimental-sqlite index.js", "package": "python package.py", "package-green": "python package.py -g", + "package-zip": "python package.py -z", + "package-green-zip": "python package.py -g -z", "packageJS": "node package.js", "packageJS-green": "node package.js -g", + "packageJS-zip": "node package.js -z", + "packageJS-green-zip": "node package.js -g -z", "gzip-1": "node controllers/encoder.js json/十六万歌曲.json", "ungzip-1": "node controllers/decoder.js json/十六万歌曲.json.gz", "moontv": "node scripts/mjs/moontv.mjs 采集2025.json -p" diff --git a/package.py b/package.py index 827deca9..9949858f 100644 --- a/package.py +++ b/package.py @@ -16,6 +16,7 @@ EXCLUDE_FILES = ['config/env.json', '.env', '.claude', 'clipboard.txt', 'clipboard.txt.bak', '.plugins.js', 'yarn.lock', 't4_daemon.pid', 'spider/js/UC分享.js', 'spider/js/百忙无果[官].js', + 'spider/catvod/mtv60w[差].js', 'json/UC分享.json', 'jx/_30wmv.js', 'jx/奇奇.js', 'jx/芒果关姐.js', 'data/settings/link_data.json', 'index.json', 'custom.json'] @@ -44,13 +45,14 @@ def filter_green_files(script_dir): return green_files -def generate_archive_name(script_dir, green=False): +def generate_archive_name(script_dir, green=False, use_zip=False): """ 生成压缩包文件名 Args: script_dir (str): 脚本所在目录 green (bool): 是否为green模式 + use_zip (bool): 是否使用ZIP格式 Returns: str: 压缩包的完整路径 @@ -63,7 +65,8 @@ def generate_archive_name(script_dir, green=False): # 根据是否传入 green 参数生成压缩包文件名 archive_suffix = "-green" if green else "" - archive_name = f"{current_dir}-{current_time}{archive_suffix}.7z" + archive_ext = ".zip" if use_zip else ".7z" + archive_name = f"{current_dir}-{current_time}{archive_suffix}{archive_ext}" # 压缩包输出路径 (脚本所在目录的外面) parent_dir = os.path.abspath(os.path.join(script_dir, "..")) @@ -107,7 +110,7 @@ def build_exclude_params(script_dir, green=False): return exclude_params -def execute_compression(archive_path, script_dir, exclude_params): +def execute_compression(archive_path, script_dir, exclude_params, use_zip=False): """ 执行7z压缩命令 @@ -115,9 +118,11 @@ def execute_compression(archive_path, script_dir, exclude_params): archive_path (str): 压缩包输出路径 script_dir (str): 脚本所在目录 exclude_params (list): 排除参数列表 + use_zip (bool): 是否使用ZIP格式 """ # 构建命令,打包目录内容而不包含目录本身 - command = f"7z a \"{archive_path}\" \"{script_dir}\\*\" " + " ".join(exclude_params) + archive_type = "zip" if use_zip else "7z" + command = f"7z a -t{archive_type} \"{archive_path}\" \"{script_dir}\\*\" " + " ".join(exclude_params) # 打印构建的命令进行调试 print(f"构建的 7z 命令: {command}") @@ -130,22 +135,23 @@ def execute_compression(archive_path, script_dir, exclude_params): print(f"压缩失败: {e}") -def compress_directory(script_dir, green=False): +def compress_directory(script_dir, green=False, use_zip=False): """ 压缩目录为7z包 Args: script_dir (str): 要压缩的目录路径 green (bool): 是否启用green模式,筛选带[密]的文件 + use_zip (bool): 是否使用ZIP格式 """ # 生成压缩包文件名和路径 - archive_path = generate_archive_name(script_dir, green) + archive_path = generate_archive_name(script_dir, green, use_zip) # 构建排除参数 exclude_params = build_exclude_params(script_dir, green) # 执行压缩 - execute_compression(archive_path, script_dir, exclude_params) + execute_compression(archive_path, script_dir, exclude_params, use_zip) if __name__ == "__main__": @@ -155,7 +161,8 @@ def compress_directory(script_dir, green=False): # 解析命令行参数 parser = argparse.ArgumentParser(description="压缩当前目录为 7z 包,支持可选参数。") parser.add_argument('-g', '--green', action='store_true', help="启用 green 模式,筛选 js 目录下所有带 [密] 的文件。") + parser.add_argument('-z', '--zip', action='store_true', help="使用 ZIP 格式打包,默认使用 7z 格式。") args = parser.parse_args() # 调用压缩函数 - compress_directory(script_dir, green=args.green) + compress_directory(script_dir, green=args.green, use_zip=args.zip) diff --git a/public/download.html b/public/download.html new file mode 100644 index 00000000..64feb013 --- /dev/null +++ b/public/download.html @@ -0,0 +1,609 @@ + + + + + + 下载 {{projectName}} + + + +
+

{{projectName}} 下载中心

+ +
+ 历史文件管理 +
+ + +
+
+ +
链接已复制到剪贴板
+ +
+ {{downloadItems}} +
+
+ + + + diff --git a/public/index.html b/public/index.html index 83d6552e..8e5f11e2 100644 --- a/public/index.html +++ b/public/index.html @@ -14,7 +14,8 @@

drpyS(drpy-node)

常用超链接

更新记录

+

20260131

+

更新至V1.3.21

+

20260127

+

更新至V1.3.20

+

20260125

+

更新至V1.3.19

20260118

更新至V1.3.18

-

20260115

-

更新至V1.3.17

-

20260113

-

更新至V1.3.16

-

20260112

-

更新至V1.3.15

点此查看完整更新记录

注意事项

总是有人遇到各种奇葩问题,像什么没弹幕,访问/config/1服务马上崩溃等等,能自行解决最好,解决不了我建议你使用下方安装教程 diff --git a/public/sub/order_common.example.html b/public/sub/order_common.example.html index 99d5a641..788cdb87 100644 --- a/public/sub/order_common.example.html +++ b/public/sub/order_common.example.html @@ -7,11 +7,14 @@ IPTV [优] [盘] +[磁] [漫] [短] [官] [听] [书] +[画] +[M] [搜] DS cat diff --git "a/spider/catvod/\345\223\251\345\223\251[\345\256\230].js" "b/spider/catvod/\345\223\251\345\223\251[\345\256\230].js" new file mode 100644 index 00000000..91cecaa6 --- /dev/null +++ "b/spider/catvod/\345\223\251\345\223\251[\345\256\230].js" @@ -0,0 +1,323 @@ +/** + * 哔哩哔哩 - 猫影视JS爬虫格式 + * 调用壳子超级解析功能 + @header({ + searchable: 1, + filterable: 1, + quickSearch: 1, + title: '哩哩[官]', + lang: 'cat' + }) + */ + +class Spider extends BaseSpider { + + constructor() { + super(); + this.host = 'https://www.bilibili.com'; + this.headers = { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', + 'Referer': 'https://www.bilibili.com', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8' + }; + + // B站Cookie(需要登录才能获取高清画质) + this.cookie = ""; + this.isLoggedIn = () => { + return this.cookie && this.cookie.includes("SESSDATA="); + }; + } + + init(extend = '') { + return ''; + } + + getName() { + return '哔哩哔哩'; + } + + isVideoFormat(url) { + return true; + } + + manualVideoCheck() { + return false; + } + + destroy() { + // 清理资源 + } + + homeContent(filter) { + const classes = [ + {type_id: '1', type_name: '番剧'}, + {type_id: '4', type_name: '国创'}, + {type_id: '2', type_name: '电影'}, + {type_id: '5', type_name: '电视剧'}, + {type_id: '3', type_name: '纪录片'}, + {type_id: '7', type_name: '综艺'} + ]; + + return { + class: classes + }; + } + + homeVideoContent() { + return {list: []}; + } + + async categoryContent(tid, pg, filter, extend) { + try { + const page = parseInt(pg) || 1; + let url = ''; + + if (['1', '4'].includes(tid)) { + url = `https://api.bilibili.com/pgc/web/rank/list?season_type=${tid}&pagesize=20&page=${page}&day=3`; + } else { + url = `https://api.bilibili.com/pgc/season/rank/web/list?season_type=${tid}&pagesize=20&page=${page}&day=3`; + } + + const headers = {...this.headers}; + if (this.cookie) { + headers.Cookie = this.cookie; + } + + const response = await this.fetch(url, {}, headers); + const data = response.data || {}; + + const videos = []; + if (data.code === 0) { + const vodList = data.result ? data.result.list : (data.data ? data.data.list : []); + + for (const vod of vodList) { + const title = vod.title ? vod.title.trim() : ''; + if (title.includes('预告')) { + continue; + } + + const remark = vod.new_ep ? vod.new_ep.index_show : vod.index_show; + + // 处理封面图片 + let cover = vod.cover || ''; + if (cover && cover.startsWith('//')) { + cover = 'https:' + cover; + } + + videos.push({ + vod_id: vod.season_id ? vod.season_id.toString() : '', + vod_name: title, + vod_pic: cover, + vod_remarks: remark || '' + }); + } + } + + return { + list: videos, + page: page, + pagecount: videos.length === 20 ? page + 1 : page, + limit: 20, + total: 9999 + }; + + } catch (error) { + console.error(`categoryContent error: ${error.message}`); + return { + list: [], + page: pg, + pagecount: 0, + limit: 20, + total: 0 + }; + } + } + + formatCount(num) { + if (num > 1e8) return (num / 1e8).toFixed(2) + '亿'; + if (num > 1e4) return (num / 1e4).toFixed(2) + '万'; + return num.toString(); + } + + async detailContent(ids) { + try { + const seasonId = ids[0]; + + const headers = {...this.headers}; + if (this.cookie) { + headers.Cookie = this.cookie; + } + + const url = `https://api.bilibili.com/pgc/view/web/season?season_id=${seasonId}`; + const response = await this.fetch(url, {}, headers); + const data = response.data || {}; + + if (data.code !== 0) { + return {list: []}; + } + + const res = data.result; + const stat = res.stat || {}; + + // 处理封面图片 + let cover = res.cover || ''; + if (cover && cover.startsWith('//')) { + cover = 'https:' + cover; + } + + const vod = { + vod_id: res.season_id ? res.season_id.toString() : '', + vod_name: res.title || '', + vod_pic: cover, + type_name: res.share_sub_title || res.type_name || '', + vod_year: res.publish && res.publish.pub_time ? res.publish.pub_time.substr(0, 4) : '', + vod_area: res.areas && res.areas.length > 0 ? res.areas[0].name : '', + vod_actor: `点赞:${this.formatCount(stat.likes || 0)} 投币:${this.formatCount(stat.coins || 0)}`, + vod_content: res.evaluate || res.new_ep?.desc || '', + vod_director: res.rating ? `评分:${res.rating.score}` : '暂无评分', + vod_play_from: '哔哩哔哩', + vod_play_url: '' + }; + + // 过滤预告片,构建播放列表 + const episodes = (res.episodes || []).filter(ep => !ep.title.includes('预告')); + const playUrls = []; + + for (const ep of episodes) { + const title = `${ep.title.replace(/#/g, '-')} ${ep.long_title || ''}`; + const playId = `${res.season_id}_${ep.id}_${ep.cid}`; + playUrls.push(`${title}$${playId}`); + } + + vod.vod_play_url = playUrls.join('#'); + + return {list: [vod]}; + + } catch (error) { + console.error(`detailContent error: ${error.message}`); + return {list: []}; + } + } + + async searchContent(key, quick, pg = '1') { + try { + const page = parseInt(pg) || 1; + const encodedKeyword = encodeURIComponent(key); + const searchTypes = ['media_bangumi', 'media_ft']; + + const headers = {...this.headers}; + if (this.cookie) { + headers.Cookie = this.cookie; + } + + const allVideos = []; + + for (const type of searchTypes) { + try { + const url = `https://api.bilibili.com/x/web-interface/search/type?search_type=${type}&keyword=${encodedKeyword}&page=${page}`; + const response = await this.fetch(url, {}, headers); + const data = response.data || {}; + + if (data.code === 0 && data.data && data.data.result) { + for (const vod of data.data.result) { + const title = vod.title ? vod.title.replace(/<[^>]+>/g, '') : ''; + if (title.includes('预告')) { + continue; + } + + // 处理封面图片 + let cover = vod.cover || ''; + if (cover && cover.startsWith('//')) { + cover = 'https:' + cover; + } + + allVideos.push({ + vod_id: vod.season_id ? vod.season_id.toString() : '', + vod_name: title, + vod_pic: cover, + vod_remarks: vod.index_show || '' + }); + } + } + } catch (searchError) { + console.error(`搜索类型 ${type} 失败: ${searchError.message}`); + } + } + + return { + list: allVideos, + page: page, + pagecount: allVideos.length > 0 ? page + 1 : page, + limit: 20, + total: allVideos.length + }; + + } catch (error) { + console.error(`searchContent error: ${error.message}`); + return { + list: [], + page: pg, + pagecount: 0, + limit: 20, + total: 0 + }; + } + } + + async playerContent(flag, id, vipFlags) { + try { + // 哔哩哔哩有自己的解析逻辑,直接返回播放链接 + // 格式:seasonId_epId_cid + const parts = id.split('_'); + if (parts.length < 3) { + throw new Error('无效的播放ID格式'); + } + + const seasonId = parts[0]; + const epId = parts[1]; + const cid = parts[2]; + + // 构建播放链接(原版B站链接) + const playUrl = `https://www.bilibili.com/bangumi/play/ep${epId}`; + + // 调用壳子超级解析 + const playData = { + parse: 1, + jx: 1, + play_parse: true, + parse_type: '壳子超级解析', + parse_source: '哔哩哔哩', + url: playUrl, + header: JSON.stringify({ + 'User-Agent': this.headers['User-Agent'], + 'Referer': 'https://www.bilibili.com', + 'Origin': 'https://www.bilibili.com', + 'Cookie': this.cookie || '' + }) + }; + + return playData; + + } catch (error) { + console.error(`playerContent error: ${error.message}`); + // 即使出错也返回超级解析参数 + return { + parse: 1, + jx: 1, + play_parse: true, + parse_type: '壳子超级解析', + parse_source: '哔哩哔哩', + url: id.includes('_') ? `https://www.bilibili.com/bangumi/play/ep${id.split('_')[1]}` : id, + header: JSON.stringify(this.headers) + }; + } + } + + localProxy(param) { + return null; + } +} + + +export default new Spider(); \ No newline at end of file diff --git "a/spider/catvod/\345\244\256\345\244\256[\345\256\230].js" "b/spider/catvod/\345\244\256\345\244\256[\345\256\230].js" new file mode 100644 index 00000000..478e4b10 --- /dev/null +++ "b/spider/catvod/\345\244\256\345\244\256[\345\256\230].js" @@ -0,0 +1,290 @@ +/** + * 央视大全 - 猫影视/TVBox JS爬虫格式 + * 继承BaseSpider类 + @header({ + searchable: 1, + filterable: 1, + quickSearch: 1, + title: '央央[官]', + lang: 'cat' + }) + */ + +class Spider extends BaseSpider { + + constructor() { + super(); + this.host = 'https://api.cntv.cn'; + this.siteName = '央视大全'; + this.sessionStore = {}; + this.videoCache = {}; + + this.headers = { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", + "Referer": "https://tv.cctv.com", + "Accept": "application/json, text/plain, */*", + "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8" + }; + } + + init(extend = "") { + return ""; + } + + getName() { + return this.siteName; + } + + isVideoFormat(url) { + return url.includes('.m3u8') || url.includes('.mp4'); + } + + manualVideoCheck() { + return false; + } + + destroy() { + this.sessionStore = {}; + this.videoCache = {}; + } + + homeContent(filter) { + const categories = [ + {type_id: "栏目大全", type_name: "栏目大全"}, + {type_id: "特别节目", type_name: "特别节目"}, + {type_id: "纪录片", type_name: "纪录片"}, + {type_id: "电视剧", type_name: "电视剧"}, + {type_id: "动画片", type_name: "动画片"} + ]; + + return {class: categories}; + } + + async homeVideoContent() { + // 央视首页推荐 + return {list: []}; + } + + async categoryContent(tid, pg, filter, extend) { + try { + const page = parseInt(pg) || 1; + const videos = []; + + const channelMap = { + "特别节目": "CHAL1460955953877151", + "纪录片": "CHAL1460955924871139", + "电视剧": "CHAL1460955853485115", + "动画片": "CHAL1460955899450127", + }; + + let filterObj = {}; + if (extend && typeof extend === 'object') { + filterObj = extend; + } + + if (tid === '栏目大全') { + const url = `${this.host}/lanmu/columnSearch?&fl=&fc=&cid=&p=${page}&n=20&serviceId=tvcctv&t=json`; + const response = await this.fetch(url, {}, this.headers); + const data = response.data; + + if (data && data.response && data.response.docs) { + const docs = data.response.docs; + docs.forEach(it => { + videos.push({ + vod_id: `${it.lastVIDE.videoSharedCode}|${it.column_firstclass}|${it.column_name}|${it.channel_name}|${it.column_brief}|${it.column_logo}|${it.lastVIDE.videoTitle}|栏目大全`, + vod_name: it.column_name, + vod_pic: it.column_logo, + vod_remarks: it.channel_name, + vod_content: '' + }); + }); + } + } else { + // 处理筛选参数 + let fl_url = `&channelid=${channelMap[tid] || ''}&fc=${encodeURIComponent(tid)}`; + if (filterObj.channel) fl_url += `&channel=${encodeURIComponent(filterObj.channel)}`; + if (filterObj.sc) fl_url += `&sc=${encodeURIComponent(filterObj.sc)}`; + if (filterObj.year) fl_url += `&year=${filterObj.year}`; + + const url = `${this.host}/list/getVideoAlbumList?${fl_url}&area=&letter=&n=24&serviceId=tvcctv&t=json&p=${page}`; + const response = await this.fetch(url, {}, this.headers); + const data = response.data; + + if (data && data.data && data.data.list) { + const dataList = data.data.list; + dataList.forEach(it => { + videos.push({ + vod_id: `${it.id}|${it.sc}|${it.title}|${it.channel}|${it.brief}|${it.image}|${it.count}|${tid}`, + vod_name: it.title, + vod_pic: it.image, + vod_remarks: `${it.sc}${it.year ? '·' + it.year : ''}`, + vod_content: it.brief || '' + }); + }); + } + } + + return { + list: videos, + page: page, + pagecount: 9999, + limit: 20, + total: 999999 + }; + + } catch (error) { + console.error(`categoryContent error: ${error.message}`); + return { + list: [], + page: pg, + pagecount: 0, + limit: 20, + total: 0 + }; + } + } + + async detailContent(ids) { + try { + const id = ids[0]; + if (!id) return {list: []}; + + // 检查缓存 + const cacheKey = `detail_${id}`; + if (this.videoCache[cacheKey]) { + return {list: [this.videoCache[cacheKey]]}; + } + + const info = id.split("|"); + // ID 结构: 0:id, 1:sc, 2:title, 3:channel, 4:brief, 5:image, 6:count/remark, 7:cate + + const cate = info[7]; + const ctid = info[0]; + const modeMap = { + "特别节目": "0", + "纪录片": "0", + "电视剧": "0", + "动画片": "1" + }; + + // 获取选集列表 + let playUrls = []; + const mode = modeMap[cate] || '0'; + const albumUrl = `${this.host}/NewVideo/getVideoListByAlbumIdNew?id=${ctid}&serviceId=tvcctv&p=1&n=100&mode=${mode}&pub=1`; + + const response = await this.fetch(albumUrl, {}, this.headers); + const data = response.data; + + if (data.errcode === '1001') { + // 需要获取真实的ctid + const videoInfoUrl = `${this.host}/video/videoinfoByGuid?guid=${ctid}&serviceId=tvcctv`; + const vInfoRes = await this.fetch(videoInfoUrl, {}, this.headers); + const vInfoData = vInfoRes.data; + const realCtid = vInfoData.ctid; + + const columnUrl = `${this.host}/NewVideo/getVideoListByColumn?id=${realCtid}&d=&p=1&n=100&sort=desc&mode=0&serviceId=tvcctv&t=json`; + const colRes = await this.fetch(columnUrl, {}, this.headers); + const colData = colRes.data; + playUrls = colData.data?.list || []; + } else { + playUrls = data.data?.list || []; + } + + // 构建播放列表 + const playList = []; + if (playUrls.length > 0) { + for (const item of playUrls) { + const title = item.title || `第${item.index || '?'}集`; + const cleanTitle = title.replace(/\$/g, ''); + const guid = item.guid || ''; + playList.push(`${cleanTitle}$${guid}`); + } + } + + const vod = { + vod_id: id, + vod_name: info[2] || '', + vod_pic: info[5] || '', + type_name: info[1] || '', + vod_year: '', + vod_area: '', + vod_remarks: info[6] ? `共${info[6]}集` : '', + vod_actor: '', + vod_director: '', + vod_content: info[4] || '', + vod_play_from: playList.length > 0 ? '央视频' : '', + vod_play_url: playList.length > 0 ? playList.join('#') : '' + }; + + // 缓存结果 + this.videoCache[cacheKey] = vod; + + return {list: [vod]}; + + } catch (error) { + console.error(`detailContent error: ${error.message}`); + return {list: []}; + } + } + + async searchContent(key, quick, pg = "1") { + // CCTV搜索接口较复杂,这里返回空结果 + return { + list: [], + page: pg, + pagecount: 0, + limit: 20, + total: 0 + }; + } + + async playerContent(flag, id, vipFlags) { + try { + // 央视视频采用直接播放的方式 + // 根据GUID拼接m3u8地址 + let playUrl = `https://cntv.playdreamer.cn/proxy/asp/hls/2000/0303000a/3/default/${id}/2000.m3u8`; + + // 也可以尝试其他格式 + // playUrl = `https://hls.cntv.myalicdn.com/asp/hls/2000/0303000a/3/default/${id}/2000.m3u8`; + + return { + parse: 0, // 0表示直接播放,不需要解析 + jx: 0, // 0表示不解析 + url: playUrl, + header: JSON.stringify({ + 'User-Agent': this.headers['User-Agent'], + 'Referer': 'https://tv.cctv.com', + 'Origin': 'https://tv.cctv.com' + }) + }; + + } catch (error) { + console.error(`playerContent error: ${error.message}`); + return { + parse: 0, + jx: 0, + url: id, + header: JSON.stringify(this.headers) + }; + } + } + + localProxy(param) { + return null; + } + + // 辅助方法:安全获取对象属性 + getSafe(obj, path, defaultValue = '') { + if (!obj || typeof obj !== 'object') return defaultValue; + try { + return path.split('.').reduce((o, key) => { + if (o == null) return defaultValue; + return o[key]; + }, obj) ?? defaultValue; + } catch { + return defaultValue; + } + } +} + +export default new Spider(); \ No newline at end of file diff --git "a/spider/catvod/\345\245\207\345\245\207[\345\256\230].js" "b/spider/catvod/\345\245\207\345\245\207[\345\256\230].js" new file mode 100644 index 00000000..89ce8472 --- /dev/null +++ "b/spider/catvod/\345\245\207\345\245\207[\345\256\230].js" @@ -0,0 +1,403 @@ +/** + * 爱奇艺视频 - 猫影视/TVBox JS爬虫格式 + * 调用壳子超级解析功能(壳子会自动读取json配置) + @header({ + searchable: 1, + filterable: 1, + quickSearch: 1, + title: '奇奇[官]', + lang: 'cat' + }) + */ + +class Spider extends BaseSpider { + + constructor() { + super(); + this.host = 'https://www.iqiyi.com'; + this.sessionStore = {}; + + this.headers = { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', + 'Referer': 'https://www.iqiyi.com', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8', + 'Accept-Encoding': 'gzip, deflate, br', + 'Connection': 'keep-alive' + }; + + // 分类配置 + this.classes = [ + {type_id: '1', type_name: '电影'}, + {type_id: '2', type_name: '电视剧'}, + {type_id: '6', type_name: '综艺'}, + {type_id: '4', type_name: '动漫'}, + {type_id: '3', type_name: '纪录片'}, + {type_id: '5', type_name: '音乐'}, + {type_id: '16', type_name: '网络电影'} + ]; + + // 筛选配置 + this.filters = { + '1': [{ + key: 'year', + name: '年代', + value: [{n: '全部', v: ''}, {n: '2025', v: '2025'}, {n: '2024', v: '2024'}, {n: '2023', v: '2023'}] + }], + '2': [{ + key: 'year', + name: '年代', + value: [{n: '全部', v: ''}, {n: '2025', v: '2025'}, {n: '2024', v: '2024'}, {n: '2023', v: '2023'}] + }] + }; + } + + init(extend = '') { + return ''; + } + + getName() { + return '爱奇艺视频'; + } + + isVideoFormat(url) { + return true; + } + + manualVideoCheck() { + return false; + } + + destroy() { + // 清理资源 + } + + homeContent(filter) { + const result = { + class: this.classes, + filters: this.filters + }; + + return result; + } + + homeVideoContent() { + return {list: []}; + } + + async categoryContent(tid, pg, filter, extend) { + try { + let channelId = tid; + let dataType = 1; + let extraParams = ""; + const page = parseInt(pg) || 1; + + if (tid === "16") { + channelId = "1"; + extraParams = "&three_category_id=27401"; + } else if (tid === "5") { + dataType = 2; + } + + // 处理筛选条件 + if (extend) { + let extendObj = {}; + if (typeof extend === 'string') { + try { + extendObj = JSON.parse(extend); + } catch (e) { + // 如果不是JSON,尝试解析为key=value格式 + extend.split('&').forEach(item => { + const [key, value] = item.split('='); + if (key && value) { + extendObj[key] = value; + } + }); + } + } else if (typeof extend === 'object') { + extendObj = extend; + } + + if (extendObj.year) { + extraParams += `&market_release_date_level=${extendObj.year}`; + } + } + + const url = `https://pcw-api.iqiyi.com/search/recommend/list?channel_id=${channelId}&data_type=${dataType}&page_id=${page}&ret_num=20${extraParams}`; + + const response = await this.fetch(url, {}, this.headers); + const jsonData = response.data; + + const videos = []; + if (jsonData.data && jsonData.data.list) { + for (const item of jsonData.data.list) { + const vid = `${item.channelId}$${item.albumId}`; + let remarks = ""; + + if (item.channelId === 1) { + remarks = item.score ? `${item.score}分` : ""; + } else if (item.channelId === 2 || item.channelId === 4) { + if (item.latestOrder && item.videoCount) { + remarks = item.latestOrder === item.videoCount ? + `${item.latestOrder}集全` : + `更新至${item.latestOrder}集`; + } else { + remarks = item.focus || ""; + } + } else { + remarks = item.period || item.focus || ""; + } + + videos.push({ + vod_id: vid, + vod_name: item.name, + vod_pic: item.imageUrl ? item.imageUrl.replace(".jpg", "_390_520.jpg") : "", + vod_remarks: remarks + }); + } + } + + return { + list: videos, + page: page, + pagecount: 9999, + limit: 20, + total: 999999 + }; + + } catch (error) { + console.error(`categoryContent error: ${error.message}`); + return { + list: [], + page: pg, + pagecount: 0, + limit: 20, + total: 0 + }; + } + } + + async getPlaylists(channelId, albumId, data) { + let playlists = []; + const cid = parseInt(channelId || data.channelId || 0); + + try { + if (cid === 1 || cid === 5) { + // 电影或音乐 + if (data.playUrl) { + playlists.push({title: data.name || '正片', url: data.playUrl}); + } + } else if (cid === 6 && data.period) { + // 综艺 + let qs = data.period.toString().split("-")[0]; + let listUrl = `https://pcw-api.iqiyi.com/album/source/svlistinfo?cid=6&sourceid=${albumId}&timelist=${qs}`; + try { + const listResp = await this.fetch(listUrl, {}, this.headers); + const listJson = listResp.data; + if (listJson.data && listJson.data[qs]) { + listJson.data[qs].forEach(it => { + playlists.push({ + title: it.shortTitle || it.period || it.focus || `期${it.order}`, + url: it.playUrl + }); + }); + } + } catch (e) { + console.error(`综艺列表获取失败: ${e.message}`); + } + } else { + // 电视剧、动漫等 + let listUrl = `https://pcw-api.iqiyi.com/albums/album/avlistinfo?aid=${albumId}&size=100&page=1`; + try { + const listResp = await this.fetch(listUrl, {}, this.headers); + const listJson = listResp.data; + + if (listJson.data && listJson.data.epsodelist) { + playlists = listJson.data.epsodelist.map(item => ({ + title: item.shortTitle || item.title || + (item.order ? `第${item.order}集` : `集${item.timelist}`), + url: item.playUrl || item.url || '' + })); + + // 处理分页 + const total = listJson.data.total; + if (total > 100) { + const totalPages = Math.ceil(total / 100); + for (let i = 2; i <= totalPages; i++) { + let nextUrl = `https://pcw-api.iqiyi.com/albums/album/avlistinfo?aid=${albumId}&size=100&page=${i}`; + try { + const nextResp = await this.fetch(nextUrl, {}, this.headers); + const nextJson = nextResp.data; + if (nextJson.data && nextJson.data.epsodelist) { + playlists = playlists.concat(nextJson.data.epsodelist.map(item => ({ + title: item.shortTitle || item.title || + (item.order ? `第${item.order}集` : `集${item.timelist}`), + url: item.playUrl || item.url || '' + }))); + } + } catch (e) { + break; + } + } + } + } + } catch (e) { + console.error(`剧集列表获取失败: ${e.message}`); + } + } + } catch (error) { + console.error(`getPlaylists error: ${error.message}`); + } + + return playlists; + } + + async detailContent(ids) { + try { + const id = ids[0]; + let channelId = ""; + let albumId = id; + + if (id.includes('$')) { + const parts = id.split('$'); + channelId = parts[0]; + albumId = parts[1]; + } + + // 获取视频基本信息 + const infoUrl = `https://pcw-api.iqiyi.com/video/video/videoinfowithuser/${albumId}?agent_type=1&authcookie=&subkey=${albumId}&subscribe=1`; + const infoResp = await this.fetch(infoUrl, {}, this.headers); + const infoJson = infoResp.data; + const data = infoJson.data || {}; + + // 获取播放列表 + const playlists = await this.getPlaylists(channelId, albumId, data); + + // 构建播放地址 + const playUrls = []; + if (playlists.length > 0) { + for (const item of playlists) { + if (item.url) { + playUrls.push(`${item.title}$${item.url}`); + } + } + } + + const vod = { + vod_id: id, + vod_name: data.name || '未知标题', + type_name: data.categories ? data.categories.map(it => it.name).join(',') : '', + vod_year: data.formatPeriod || '', + vod_area: data.areas ? data.areas.map(it => it.name).join(',') : '', + vod_remarks: data.latestOrder ? + `更新至${data.latestOrder}集` : + (data.period || playlists.length > 0 ? `${playlists.length}集` : ''), + vod_actor: data.people && data.people.main_charactor ? + data.people.main_charactor.map(it => it.name).join(',') : '', + vod_director: data.people && data.people.director ? + data.people.director.map(it => it.name).join(',') : '', + vod_content: data.description || '暂无简介', + vod_pic: data.imageUrl ? data.imageUrl.replace(".jpg", "_480_270.jpg") : '', + vod_play_from: playUrls.length > 0 ? '爱奇艺视频' : '', + vod_play_url: playUrls.length > 0 ? playUrls.join('#') : '' + }; + + return {list: [vod]}; + + } catch (error) { + console.error(`detailContent error: ${error.message}`); + return {list: []}; + } + } + + async searchContent(key, quick, pg = '1') { + try { + const page = parseInt(pg) || 1; + const url = `https://search.video.iqiyi.com/o?if=html5&key=${encodeURIComponent(key)}&pageNum=${page}&pos=1&pageSize=20&site=iqiyi`; + + const response = await this.fetch(url, {}, this.headers); + const jsonData = response.data; + + const videos = []; + + if (jsonData.data && jsonData.data.docinfos) { + for (const item of jsonData.data.docinfos) { + if (item.albumDocInfo) { + const doc = item.albumDocInfo; + const channelId = doc.channel ? doc.channel.split(',')[0] : '0'; + videos.push({ + vod_id: `${channelId}$${doc.albumId}`, + vod_name: doc.albumTitle || '', + vod_pic: doc.albumVImage || '', + vod_remarks: doc.tvFocus || doc.year || '' + }); + } + } + } + + return { + list: videos, + page: page, + pagecount: 10, + limit: 20, + total: videos.length + }; + + } catch (error) { + console.error(`searchContent error: ${error.message}`); + return { + list: [], + page: pg, + pagecount: 0, + limit: 20, + total: 0 + }; + } + } + + async playerContent(flag, id, vipFlags) { + try { + // 解析播放地址 + let playUrl = id; + if (id.includes('$')) { + playUrl = id.split('$')[1]; + } + + // 关键:调用壳子超级解析 + const playData = { + parse: 1, // 必须为1,表示需要解析 + jx: 1, // 必须为1,启用解析 + play_parse: true, // 启用播放解析 + parse_type: '壳子超级解析', + parse_source: '爱奇艺视频', + url: playUrl, // 原始爱奇艺链接 + header: JSON.stringify({ + 'User-Agent': this.headers['User-Agent'], + 'Referer': 'https://www.iqiyi.com', + 'Origin': 'https://www.iqiyi.com' + }) + }; + + return playData; + + } catch (error) { + console.error(`playerContent error: ${error.message}`); + // 即使出错也返回超级解析参数,让壳子处理 + return { + parse: 1, + jx: 1, + play_parse: true, + parse_type: '壳子超级解析', + parse_source: '爱奇艺视频', + url: id, + header: JSON.stringify(this.headers) + }; + } + } + + localProxy(param) { + return null; + } +} + +export default new Spider(); \ No newline at end of file diff --git "a/spider/catvod/\346\236\234\346\236\234[\345\256\230].js" "b/spider/catvod/\346\236\234\346\236\234[\345\256\230].js" new file mode 100644 index 00000000..ea5846fc --- /dev/null +++ "b/spider/catvod/\346\236\234\346\236\234[\345\256\230].js" @@ -0,0 +1,365 @@ +/** + * 芒果TV - 猫影视JS爬虫格式(第二个版本) + * 调用壳子超级解析功能 + @header({ + searchable: 1, + filterable: 1, + quickSearch: 1, + title: '果果[官]', + lang: 'cat' + }) + */ + +class Spider extends BaseSpider { + + constructor() { + super(); + this.host = 'https://www.mgtv.com'; + this.headers = { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36', + 'Referer': 'https://www.mgtv.com/', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8', + 'Accept-Encoding': 'gzip, deflate, br', + 'Connection': 'keep-alive' + }; + } + + init(extend = '') { + return ''; + } + + getName() { + return '芒果TV2'; + } + + isVideoFormat(url) { + return true; + } + + manualVideoCheck() { + return false; + } + + destroy() { + // 清理资源 + } + + homeContent(filter) { + const classes = [ + {type_id: '3', type_name: '电影'}, + {type_id: '2', type_name: '电视剧'}, + {type_id: '1', type_name: '综艺'}, + {type_id: '50', type_name: '动漫'}, + {type_id: '51', type_name: '纪录片'}, + {type_id: '115', type_name: '教育'}, + {type_id: '10', type_name: '少儿'} + ]; + + const filters = { + '3': [ + { + key: 'year', name: '年份', value: [ + {n: '全部', v: 'all'}, {n: '2025', v: '2025'}, {n: '2024', v: '2024'}, + {n: '2023', v: '2023'}, {n: '2022', v: '2022'}, {n: '2021', v: '2021'}, + {n: '2020', v: '2020'}, {n: '2019', v: '2019'}, {n: '2010-2019', v: '2010-2019'}, + {n: '2000-2009', v: '2000-2009'} + ] + }, + { + key: 'sort', name: '排序', value: [ + {n: '综合', v: 'c1'}, {n: '最新', v: 'c2'}, {n: '最热', v: 'c4'} + ] + } + ], + '2': [ + { + key: 'year', name: '年份', value: [ + {n: '全部', v: 'all'}, {n: '2025', v: '2025'}, {n: '2024', v: '2024'}, + {n: '2023', v: '2023'}, {n: '2022', v: '2022'}, {n: '2021', v: '2021'}, + {n: '2020', v: '2020'} + ] + }, + { + key: 'sort', name: '排序', value: [ + {n: '综合', v: 'c1'}, {n: '最新', v: 'c2'}, {n: '最热', v: 'c4'} + ] + } + ], + '1': [ + { + key: 'sort', name: '排序', value: [ + {n: '综合', v: 'c1'}, {n: '最新', v: 'c2'}, {n: '最热', v: 'c4'} + ] + } + ], + '50': [ + { + key: 'sort', name: '排序', value: [ + {n: '综合', v: 'c1'}, {n: '最新', v: 'c2'}, {n: '最热', v: 'c4'} + ] + } + ] + }; + + return { + class: classes, + filters: filters + }; + } + + homeVideoContent() { + return {list: []}; + } + + async categoryContent(tid, pg, filter, extend) { + try { + const page = parseInt(pg) || 1; + const baseUrl = 'https://pianku.api.mgtv.com/rider/list/pcweb/v3'; + + // 构建查询参数 + const params = { + platform: 'pcweb', + channelId: tid, + pn: page, + pc: '20', + hudong: '1', + _support: '10000000', + kind: 'a1', + area: 'a1' + }; + + // 处理筛选条件 + if (extend) { + if (extend.year && extend.year !== 'all') { + params.year = extend.year; + } + if (extend.sort) { + params.sort = extend.sort; + } + if (extend.chargeInfo) { + params.chargeInfo = extend.chargeInfo; + } + } + + const queryString = new URLSearchParams(params).toString(); + const url = `${baseUrl}?${queryString}`; + + const response = await this.fetch(url, {}, this.headers); + const json = response.data || {}; + + const videos = []; + if (json.data?.hitDocs && Array.isArray(json.data.hitDocs)) { + for (const item of json.data.hitDocs) { + videos.push({ + vod_id: item.playPartId || '', + vod_name: item.title || '', + vod_pic: item.img || '', + vod_remarks: item.updateInfo || item.rightCorner?.text || '' + }); + } + } + + return { + list: videos, + page: page, + pagecount: json.data?.totalPage || 999, + limit: 20, + total: json.data?.totalHit || 9999 + }; + + } catch (error) { + console.error(`categoryContent error: ${error.message}`); + return { + list: [], + page: pg, + pagecount: 0, + limit: 20, + total: 0 + }; + } + } + + async detailContent(ids) { + try { + const videoId = ids[0]; + + // 获取视频基本信息 + const infoUrl = `https://pcweb.api.mgtv.com/video/info?video_id=${videoId}`; + const infoResponse = await this.fetch(infoUrl, {}, this.headers); + const infoData = infoResponse.data?.data?.info || {}; + + const vod = { + vod_id: videoId, + vod_name: infoData.title || '', + type_name: infoData.root_kind || '', + vod_actor: '', + vod_year: infoData.release_time || '', + vod_content: infoData.desc || '', + vod_remarks: infoData.time || '', + vod_pic: infoData.img || '', + vod_play_from: '芒果TV', + vod_play_url: '' + }; + + // 分页获取所有剧集 + const pageSize = 50; + let allEpisodes = []; + + try { + // 获取第一页,同时获取总页数 + const firstPageUrl = `https://pcweb.api.mgtv.com/episode/list?video_id=${videoId}&page=1&size=${pageSize}`; + const firstResponse = await this.fetch(firstPageUrl, {}, this.headers); + const firstData = firstResponse.data?.data || {}; + + if (firstData.list && Array.isArray(firstData.list)) { + allEpisodes = allEpisodes.concat(firstData.list); + const totalPages = firstData.total_page || 1; + + // 如果有多页,获取剩余页面 + if (totalPages > 1) { + const pagePromises = []; + for (let i = 2; i <= totalPages; i++) { + const pageUrl = `https://pcweb.api.mgtv.com/episode/list?video_id=${videoId}&page=${i}&size=${pageSize}`; + pagePromises.push(this.fetch(pageUrl, {}, this.headers)); + } + + const responses = await Promise.all(pagePromises); + for (const response of responses) { + const data = response.data?.data || {}; + if (data.list && Array.isArray(data.list)) { + allEpisodes = allEpisodes.concat(data.list); + } + } + } + } + } catch (episodeError) { + console.error(`获取剧集列表失败: ${episodeError.message}`); + } + + // 构建播放列表 + const playUrls = []; + if (allEpisodes.length > 0) { + // 过滤可播放的剧集(isIntact = 1) + const validEpisodes = allEpisodes.filter(item => + item.isIntact === "1" || item.isIntact === 1 + ); + + // 按集数排序 + validEpisodes.sort((a, b) => { + const orderA = parseInt(a.order) || 0; + const orderB = parseInt(b.order) || 0; + return orderA - orderB; + }); + + // 构建播放链接 + for (const item of validEpisodes) { + const name = item.t4 || item.t3 || item.title || `第${item.order || '?'}集`; + const playLink = item.url ? `https://www.mgtv.com${item.url}` : ''; + + if (playLink) { + playUrls.push(`${name}$${playLink}`); + } + } + } + + vod.vod_play_url = playUrls.join('#'); + + return {list: [vod]}; + + } catch (error) { + console.error(`detailContent error: ${error.message}`); + return {list: []}; + } + } + + async searchContent(key, quick, pg = '1') { + try { + const page = parseInt(pg) || 1; + const searchUrl = `https://mobileso.bz.mgtv.com/msite/search/v2?q=${encodeURIComponent(key)}&pn=${page}&pc=20`; + + const response = await this.fetch(searchUrl, {}, this.headers); + const json = response.data?.data || {}; + + const videos = []; + + if (json.contents && Array.isArray(json.contents)) { + for (const group of json.contents) { + if (group.type === 'media' && group.data && Array.isArray(group.data)) { + for (const item of group.data) { + if (item.source === 'imgo') { + // 提取视频ID + const match = item.url.match(/\/(\d+)\.html/); + if (match) { + videos.push({ + vod_id: match[1], + vod_name: item.title ? item.title.replace(/|<\/B>/g, '') : '', + vod_pic: item.img || '', + vod_remarks: item.desc ? item.desc.join(' ') : '' + }); + } + } + } + } + } + } + + return { + list: videos, + page: page, + pagecount: 10, + limit: 20, + total: videos.length + }; + + } catch (error) { + console.error(`searchContent error: ${error.message}`); + return { + list: [], + page: pg, + pagecount: 0, + limit: 20, + total: 0 + }; + } + } + + async playerContent(flag, id, vipFlags) { + try { + // 调用壳子超级解析 + const playData = { + parse: 1, + jx: 1, + play_parse: true, + parse_type: '壳子超级解析', + parse_source: '芒果TV2', + url: id, + header: JSON.stringify({ + 'User-Agent': this.headers['User-Agent'], + 'Referer': 'https://www.mgtv.com', + 'Origin': 'https://www.mgtv.com' + }) + }; + + return playData; + + } catch (error) { + console.error(`playerContent error: ${error.message}`); + return { + parse: 1, + jx: 1, + play_parse: true, + parse_type: '壳子超级解析', + parse_source: '芒果TV2', + url: id, + header: JSON.stringify(this.headers) + }; + } + } + + localProxy(param) { + return null; + } +} + +export default new Spider(); \ No newline at end of file diff --git "a/spider/catvod/\350\223\235\350\216\223\350\201\232\345\220\210\347\237\255\345\211\247[B].js" "b/spider/catvod/\350\223\235\350\216\223\350\201\232\345\220\210\347\237\255\345\211\247[B].js" index a699e4e1..2b940d50 100644 --- "a/spider/catvod/\350\223\235\350\216\223\350\201\232\345\220\210\347\237\255\345\211\247[B].js" +++ "b/spider/catvod/\350\223\235\350\216\223\350\201\232\345\220\210\347\237\255\345\211\247[B].js" @@ -8,4 +8,4 @@ }) */ -22DAA16367B92CC030386164898346DD1C0609E5E4216F8DBCAECCF35EF70F55C25CE8F59EEED9164FDD8C3D453708E101E456346123448D658C94C0869AB7674CE7EFBD6258F1FEC412F8941F11017D143F2C4CF0FCBC32A3002C132208834E3C52CBACA4AED32A44BD9B515936CC0394704099B511F18BF3F35BCDBDD1A76E49A45DD5DA51ADB54014B3284BA7A5BB9A69A60E35966EE12ACFCD5D4AC4EC96CFB44F8B15BEA6CEC221FF7B90DA9FC027927A6505257C3E562934F81FCF81CF6ED36E981BE2A608F5B01B6138500D16C720B560EAD4D223A280E54FCE22838AD070DD8219D1D5FC6D6AD204C5E584B49F419C954E944CD189340FC87B01EE424399AA4ED45AAF0B35E10D9B36BA9B493951BEC257025998ACC0C8115CCED73BFCBDC1FFD3DFD6DAFB1A065DDF8B7CCED61C73FFCC02076BA49FF37719AC60E60F11A75716A0BD6AB221A413F0E80A7A8F58C670B5ADFA7A914CB105EB8364CBF5763D2A8BF14072D906D715FC779F36987910C29832E56A9573762ACB105EFEF5119892059422CD79300EF60408F05A1DB8AD64E6B53C76AB9A82BA2DB9F266EE5ED7C00401B63AF4709C71C16D11E0E451D79F96CDC0F5A9379988B6413C9F6B41A0343DA3869AE33A69312BE6DF05CD5BDA8783AE53E3843A45A25A4A4C2FFAFA48285F419EA9909B3B43A89C53CC5F5F58EF747023111CBA085BF16F8C48A46FDCCF1324F2160C433AC7092ED6966E7B84D49C2809F015A45A2B2F8326C0066A247F9A630D74BAEB068918CF7FA09EEA004412A9314D6078C4157C566523EFFC85678C063A36FDAA9A189FAACB6CCDA183D4DE30F4094CEF3CC9613D5596017206BCCD7D9EFFB34203B7C3E60F33E96D5A288E494E030C79E81CD6698C494AEE621467FCC615DF4B4CB587BC4BD594EA26668C06FEB662676C5110D7A14D90F8D1B0921964E6F48BC738B160F3AE0658CB9487DDC904BBC7CD80451975FDCBBBF5CB59F7DFBF034F8B738375A99D1416696BEAB17C924E5D2D37B0FE5C116FABDACB5C99F245C867A96D21792FF7C5622CCC88C2EA0BE56B3DF069731B22C9116DA6A9D75AA8CA50E9555FE32EC237A48A5364F3E09E5D63961EEBF795139CAD4D79810EEA914B014D0A05E4D9CAF883EF7D94E2432FAA0B657FF9B71049D7EA0959096B941DC18792A1746ECBF91F5837728DD7DF365E7D168FA39C61DC65F7351A35D7172E55F10F03197686ED6AF14D8C5AEB941A35DAD514D58D84C3D920DA793D5CFEE71C25E53EC21C1936FCE766119A6492CBB9B67925B9DB30B75E2391D8DF684AD5DF08B64F3DDFC31FEAAF35B5CA72BB73BCA95CA621C9D0C03E877B115EF2B3988078E83AF83D2AC691092C7AAF8D45557CF4003D4FA67A81C880EBCD8318B37C5219808335CB7F8664E176769B1BACCAB58E0E1C8B2F7B06D339FB38718653D352F6A5B2FBEDF758F2AD54B2548DE7A343B23B8E7B437C1AE212CF0DE693DD84ECB9D922B756FCBF8F67C143A90671A7A0DFE8A08063F24539FCD1579DAAC2A6DF8BA36B6DC901FF6AAA05763A10334ADF0381DF233A56FD68CE17F5B3279C1F5CAB7CB1D8FB5C490DD23B77D895061426287D96106858325AA4E666605D6A1488781190781DE82AEF9EFD0700D189757D64634844EE0EB21F152401DA3527A75B29519B9115B9A5CA3F2C181551079205A874877E61BCF897C2444DAE41DDC644D8F2635211C6A1851AF13DAFDE63DD929ED87434B8C2E3BC0AF17040ABC43EEE2F7170680AD7194194EA04AA55905F746778DB544F9F06013E3276BF535FE4255383F31BDE7F5F9CC241A7EE71CC839F57C6A8356B89BEF0C43975A1381FAA09D7CB1C0ACC85FDFACE418B994FA2A86E0F4B55E17E40830FF0236F140455E44063D126F164DAEC12BA124D2D212D52311103353E5D7E6BFCA89923F294673BD43CD5DE58C051BE48F7CBB49687F675A4EE1A407738713130868401CF272324A3675A1DDE2960FFDA9065B1E5479E0B62B5E36C8440FE50C2AAF5EDF7FE1001869A9A93763B9B805B2628E6759A1DBD1A68465709D52B808701E5ACB87E68AE661BE8B3CED65D6D57D23F3680E21C2A3766D0A13AEE06E042C973A04A1D13AB4E320AE59DBDCCE0A5DE7B3E6B6D70DCA38910B3267F21CFC6D5457B9C119E1AEA4CE2DEEBDFBBAB3731FFF612426DF576D4A82AC5B2C043346B152E1632635C708D6BCD21902666EA42CDF002D526D91D37576197FDE6721211C47EED3F1D753219E8730EF4299D321AEBA5115C4C99DD532B34044CC1C2E65D4421BB4F28B9A10A0FB6B1D23B1C1867B698C9DBEE0600FB29AEE4D5661D128BA572B285AD7E646F689D93CC5C1F41DF28CA28A402BA3036745A6FE8C783E4E5104CBFCA723F28FE4377A5C9398592BE67BB2F5E8203C76DE5197B39B1477DF3FE167E667B77D6A965F98B2CB6AA37C8AE5707AA27B3D6367277EF6D4F90DF1E60929189CBD8215AB3510D1A62935D672D7DFC2BA16E204B77BD2E8BF34FFD6AB21A87D147349F58BD8F9245B8B396E0DFCF23DB0DDEE7A32DFAEA6E1ECF18DDC32E868E1C2A54CABCFE97D08CAC8856F9353A21E3E840AF5DD56450F474F1AFFA6A4AA5538B903C2B696618BEBD087612F0800B22A721993A7285F462F30CA340A093A37E70F49FCA876A3096C23A2E08DB4619B5C48DB7F3FEAC8DEBD60642424511DC26DCE6B749FFF58C3905D0E095768771EF820E0944BCD2DE87F551E388DAE0CC5E81653CC9F82411E6B84B358B6F2FCF3933A356AEB2E799163E163C6A7D79FBAD027291C77D630A652270F10ED4DC9F7F6AF04FD21FC7257C6079DF692170E0065674323313911BE264C40A0070BDD9E631C5893070134DAD54D53C9A47694B3F731AD6A6D45D9661A9A5A0C89614352199E3E38911D7649D40ADC731DBF8B100644BB3D836CD15A534CE876D98E92810E0A22FC410F6CB55D224689E2C7FC6C6A357D2143105CAD937916EBBE2B2F683237566EECE0CA25A2FC51283E43C0DE5A9875BD13C94E3EE9FF71B77E5A410CECF084565F535DDAC9D1023BAF6F43D3850FC17E916B277477FA144978694FF61852C77805C28D29C8A19AF52CF3C0666C324AF9D77034C1FD1581179C063C0B9AC1D72A038F4B85214B2226ADF8702B940EC21F6F5C25A432E317BEFBDCBD735D60F8DBC1AB833B969E351C113114E5AEC7982BAC7F4B095C77EEBE42FBE837426409DAC0EA79589CB07B0437AEDF524D14BAF59A3DCBC8D4BF2B1C4F45C6F1B9D8C76427D1E242DF18A4AE933A8A3A4CED486BD0EA9277E093D36463C61E0DDE2CF5BA7E103689FBF7184F547BECD5AD25C635FA921FAC0ACC9903B448812BBDF67DCE05C28D29C8A19AF52CF3C0666C324AF9EDA4202C51A8100878F0259D6E78D637545421AF73DF114174D1B0CDAA710C0D4671D9936E9CD93307F8F879CB2CF3C50A1A7BC177FD4D38ADF97ACB09576F2BD91EF1D4A4244F665BD0969D372749A305C28D29C8A19AF52CF3C0666C324AF923815C408A0E8F777CD2583FE2062F438A7682D6690478D1BED2315E3F00E866C1AB833B969E351C113114E5AEC7982BF0730EF2A4111EA27B25DAD22F3FB483820B695209A37DDFC70561A7EF86EDD22EF31B834BF01F50873B6913E72AB018D5093F092F2C2BCD802D2B91EF5E179416255AE46B97E9145579367068007662EEF1ACFB3BFB7B5E448FFB764115BE3141FF35528F85373C71167B13AD82167895F6029FE858CD17B9B43029811056962059891A700283696F8175F1D805F14855A406C54CF4749D4E2BD5C00F782BB870946FBAFE98A4F2DA1EC3F4F1016FD105C28D29C8A19AF52CF3C0666C324AF902FD756803F2B47F0C54594198D273D6E2BEB8A2CCBD33215C84EDC6A48412AD9219952C8FBAE7D669357BC7B422785F41FF35528F85373C71167B13AD821678DE1AEDC5C49478E0D029291F3A98D315825CBEC7A61D64012300CD34FE07C5CA54BF1E91E1D1C6310035B73DA20D0D9F05C28D29C8A19AF52CF3C0666C324AF9C11E23C1D743CBD139E1E1F0459D4E4A0B58B7D69D72AA79325628E1B31F394F05C28D29C8A19AF52CF3C0666C324AF9B9EE558BCD2C3970430837EC85D734B87F95E78BF6A283BA9988B521A47A41BDD9C0EC00939DEE2574F7875B3B098A408873C10ECE16421EB807A62B13FCAE375BB74D628AECA3BD9C19C2A8D50946E805C28D29C8A19AF52CF3C0666C324AF9FD3CE85280A2D758C01B7D8CE889B8C24991041F9FEAAC90A271E9DB4D0BED2958E3028B6D30B2014121B7897A48A428E06B5A60EDFE80A70E8B1222310C7CFC1A639AE85B45F3836BB04BAB585541EBA80FC5AC5FB7FA13C70EB0D1CA40190FB5F8FE78C8D283EE9CF7E26ED287E1B27E90D85B3C77C0FCC49FE301727799C5E626F6BD4395121CEE9D11BD9160BF2201EA2D833D9F9150418E77D3A3B4F860505443C633C4EE6C4CBF3709FB8FCC64F400E3A9B3F97F282FB88C60808D00293C1F733615FDAAA5C180D12C073A8412BBD380BB60DC4773AC037BBEB78103C8121F28FE4C8E17F6C6F4B19A7DC5B9B5A189FAACB6CCDA183D4DE30F4094CEF3587A484338B82B7DD143C594401E4D0DF7FD608DB622A03F3C0EF94269EB2C7E93CB72F3764EA83AFEB2E062C34D2354BA63582E8187F1487390D031EAF2C8EBE6415815F5ECD7C4E72FBE4228B3E12F635136D3BCCA82845D49649510BB90BA43A333792275C86A0372ED608A6E22882FBCA2C83A2F7ABD0DAA21D443EE0BCFD4722C78C61A06445CCA2807FF3130A291AE10029A084CFE86851602EBFD2D1BE17240295A7E69C1DDA7BB48462DC9F3B19066037BA4316A1FF10788AB670D604FA812C6110E0C68D4B6BBA93A6A95D7F098755DBB6CF7E2CB170A3F6B271C5515514FB01789F488B28BEB235BE05CDA8D83D971DB00CF7A845EE904833D261E41FF35528F85373C71167B13AD8216787D57B4BA5CBB54BC268E0E20ABE595AE56F2387DA935EB666F3D77C59FA73CBBEA676F58A843E809AD48AC102EE1DB12A206898ED94D69EF0D868FDF7355E993862D80DC04D43382510D88042A0555CA259A39CAF911836214412D0C49A0E5AAD65666C9BC11F34999A4BA5902BF243C56F2387DA935EB666F3D77C59FA73CBB826FCD5378B557CBC1EBF7C22EB8631896C695C97340A74AB3740075489901A94722E419647BBD294C83945AE9EBB1FF5501AF1AC28CA9F579D326D8BE740DFB686F6F8EC98D1E5B61EC1123698B323DC0EA79589CB07B0437AEDF524D14BAF5545C9BCFF3DB20040F337F1F3906CD73CB78210273B70CC470466232637EE18E5E6AAE9AD4359D929AC271390946C40530B78BC343E2FFEE8A800D6F977C4056D2D37E4D6435E6A379CC5D16789CEC38097DC23581CDFA72EA11715CF62460D2D0DD57862A8897B5BA6D4F865FF0C7CE155CB1B0940975ED691FF19D416975AA26BD2A8FAF6D3BF47B4A005CD37F9C67A5E3D42D6F12CEE01091123478F0FED450BA1A701C48D9DF1D2C7088230F6BE5C8A4C1D328CF4CAF9B1B4208F8D295ECB44E688B0A2A646B2A1B815602DBD3EFFADE9A221E638E8515F390BD45EF8EFC0CB5D4C724E34BE62176CBAD3B55BAE0C40EC1CEFB0AEDF4F020CEFFF037BBAF92FEE5FDA909FB5F3351B903D965D6A45D9816F1139D03D9B9B53B69FAD5A31FDDC0666F1C85261F319EEFCA4D133CAFBFE328F8D948E781D417768D73F4EF360A4E5231E1C0881E56399787A22C2F68E27C6E3512E8D648B6D80ED3DB9166F41459DB9CB42BB93B94E626E3D2265C43E37FA3BB1FB8FEC735906EE68269E6F62D80AC7470E0B37B370AEBB2D49A4873E985EE3FECCCB41DBB671215B494FF18FE4EAA26A773012EF68EE008312BF91670BF13F16911D60DEE8AD0322286C713CFBE728A7FADC8C9381B8538018D173BE01CCA64D3E40645572AFF0F752AAF9F9659382845A813E9D4B9BFDA2D4B16D2E24332F42FCE55C031F02ECFFAC89B6AA504B445AD91C0709DC904A1530B2D7375226D33C653C73A18C7D752BA298969EBCD743654E9CC95C0A937E256F24D36148E9D70FA86C2C01CED746800DB5CA1B54A6F8D5892E1C270732993632595921DE2663DC0A230CCC13B4DE3312F941857E28E162653A9970AE066AF1F596122DBBDBFB30819FE0EC4A2C71E74CF86B3AE9F11A4162F96AEC0BF88D79A2A2E9D049C1F290EE19169E42E461C32F3D48DE379AF02366B794D540CEF1A6E3896492BAEA61CB5032811D9D0DD1FC788D2056093F3E14D5B1CD6CA96C7543F8D80E19E4C467306743F21A7D7D2C18DFC2DDEDE608F9E1DB7C4AC7CD6FE94A3C64C24E17240295A7E69C1DDA7BB48462DC9F3B19066037BA4316A1FF10788AB670D60C3DF3306F19F62A69D88778E3EEB1A55C83404C7B4F1157290630B5A5A6AD34BBE48F26AE5115FC5842736DC21532D402EEA945DE13F572031A713FEE8E3D47CAA36F7B05174FF2D631FFAAED61D4E56A4BB744185C2F984E76570E7DAD0C237F30B15B68A590B5BC9697A257A7F7EA1006E6D266E3754859E3795529DF707AF3617E0A47399AE2DB893FF654E1253277FC916C5FFAB6111CDBFEE42BE653FAB9B78EDBA81BD1F48A9203788E42D1BEB781B84292D84455D1D17611E6D910C1389EED1588D9EAEA076ED82F2151714C92D7C04F94AC6FC18146CA1D3F994CC7AB79DA78175FB9DADE105FFB7AB95A2054286EF4C678568C78461AB5D51B4417F787C8DFB3ABC1E8A3CF0AD326DCC7AB3EE029463C9FBC16EFDD9017813901A39ECAC0C74E271B71E42660BF576A264245B3F4377D43C9098D14931988DD6B6823362A524DA3F8A0D6AA52D7D1558FB31536876B8E507E34E01F7AC9BA1743A8DCB91E2D46D1886067EDACE62F948771B226DA87F5FC43CEA2E8290108C3B5BBF64B83285E79295D88A1F304A380BAC17300FB2FD64E1FE0CE92BE4CB18715E9FC0EEB2EFFDA222433188E99B05CEFD251F92CBDF22E35C7EDF719ED0D6BE20D6C0EA79589CB07B0437AEDF524D14BAF5CE226292D40B24F01CB97C7712832F9729F02DEBA2C6FC6C0A226DD6D6CAFE160682713ADBB2E18FE4F8906C6F8B768A88B58AA4259142AED2333AE73650E773BEC40015B91171B1B4BF9B8C68686C647BDFC2B4AA6596AF36D882FC3464D6DD6FEB6ADD5BF224ACCAF68D2E341BE4F041FF35528F85373C71167B13AD821678839FA87F83B69C43DFCE83C4CAF2363519BE2624D4E5842393EFF18C8037383A84E2231B6D8F27B045B05DCF0EB63ABE8BF5EBFA3D338B22261D067A93A3788DFAA3EA2E7E0AEFDABF5688CCD0CCD0CD91A99CC73FE6AC204988E24A3B04833083D8AAFCAE69DB47846B1CE94908CDC97938DB809BD048C42A7C658F79A1934683252CCC8BCC788E65B6D4193D146DE58C0B34DFF1C76B4FE156845B0B813FE2D236FA51F75C464985542B9ACBE0028DF379158CA32475A832BD2CCDF52C3062B373A8F1886BC03AB1A094DD4B6F27E7963D71503922CE66A690501DEE9BD3367CC60FD3E30978829A3984400CE1AE34EEF8CD9EC26A0156E18A178835C6832154195F1472B7F22741067B06F04977491B80CB2C5860F0E378BF71A9AE4B4A0E740FD06E960762AAE6D40029E471A4079837A27733314C53A43FF2D09C81CEAFE05B9C8C503F854AC91A08D2E69F30945FE450867D1838A593A118163576A28303DF9B1B530BE2CB3B00F56C38F4B62E3C3289C6AB4C1BDFE060318CC46CFDB5CACAFF5619000C9FDD8922D513366BEBBA2A4CD70DE94582FE2FCF63EDB67AFB3289F1ADE014A195CDEB1E8567B2B68FA7CF291DE2B654DA293C98F6153E68037FC5071BF0638FAF5E4F840ABB09884238973DB688602C293D9F34D93996FBD26757C352E58DB8BDD9611AF68206B58C6F051C8705C63A7D84DB01D67CBCAF2475464A6A2627F7CC9FD0DA332C5B3E94E22497DC844432857CC5897F887585AD4F31FE8589766FF04B0727331E6083B806358790AE1F73B7047E5A7841A20693B384885CC142AD514C525256BE613C81922C38649DB4D8549E36A1480A56098BB86BAF4D2DBAF5B822624B0F8971D96C9605176997D8E5BBF1A50B35002A949FEAD7DDA87F829357FAA35839D66F45BBF9D8E8037F7EC7CEFC5A18D3ACFCFA94473A78DE0BF2001A7DA4183F150AD0D83C818FF5209EA012ABB739F60DB45A475962260BDD0B104C571FF651B3C1F11A8A08063F24539FCD1579DAAC2A6DF8BA36B6DC901FF6AAA05763A10334ADF0385E56C949B1B206ABFBF60262FAB9102B05C28D29C8A19AF52CF3C0666C324AF9B1F2A113F06D7604992431034279DAF7753FA6773CFE21D73B9E92A71EF550CC87AB3605FE7814973B70D8CE3CAC6F6EE863FB281EDC96577EBF427359A2D830E6ADFD29D0D88B3A35000DCEC23F09E37F6A7AFBDA97DD8330204EBAAF12D7171A9F69E8FD6C892880BF6B4DD8ECAE16B5A5D8A7A199200C96C845E979332EB08CC1FA5E47BB51E37B997167ADB099CFA2F5D29DB3E9EB3B5108F077A9FB201746569D8739F572E393B2540CE575EDC8B4C6F63370EDF14C2358EA5C4B093D0E0772CECA542DF9C788BA436D257E27531715DD6A51AF22FAF0106700C6AA25B7C7F70B246C1A0B1F1DD0E52FAA3F6CB399BA5EB3C2E2ECB93839359980000E4774E93436950CEB9A63D50D9456D2C7ABB517904BAB1AC82DFC3DF59A368F326C206EA092B7B0082446391CBD569B27B2045D8CB3A893F585DD6C388D8F42FA956D0D58184FA0C49524FB3FB5DE394EE84D8C8E43111C101B5AD062C1B32E4C64A7CF291DE2B654DA293C98F6153E68036872A5B756D2C33E7C7648B0968A4B75C0EA79589CB07B0437AEDF524D14BAF52E4E8E86F5EEA7E42585F3B0454043EE82DB3B526DA6C9086CA46442AA138D33B5134B996B001789553E8884B19AA1B002E84A5AA060C9F3067FDA1C4F7BDCC0393B19646FC6FDF78F92E9C9A297E196BD85CABE3173FCA65B31925C0C243FA2C1AB833B969E351C113114E5AEC7982B89F742F9DDFFF15846D8E44E4648BED04F14C6DC450E8D9B88147E78BFD30F800C9340A2D88138AAB98F9A40EFC416D19B4AB5C66E214C23A74331E3FB2D2F2856ABD0D8FFDFAEC9992B63BACFA0748BC804FA53CB9BE97C9D3B9359A159A1EED0E4DBEAB5409E58E29F1DD3F317CBE28A7615C7DA896DC451E665826D72BAA27532D536869DF2F45B6A1B93A7CDBDA65E2F214AF4C1A8C9B25AC39F4E2F4853E892E7A4F0D809064795567CB2E5DBCCB3FB0DCC82525FB3776CDB6132A2EC199AA70D2C558AAE430C3EA1DFF8F09BEE865E5ABF07563C85686B2A0C1A8339620E729CDCDE0A844B547C703BE49E58A0154E00664F661D9C3612B81C429F813E76A8A0486D27CC7EAC0E6946979E9402B2B073F873FA0C7BA888BA197636D87C4C7D4741FD68FAD1CD265EE3E89F32C0DB015701A09068F04BD0505C58EE239217A33414E568A610D2D4AC946BD4AEB5453F958F832284D359AAFF3D6F39C788E78D4A9664A0F4D79F5AFEC0610ADA27E26E348FF3A480F16C1BFB74B3C41F825075C8FD9A15796E0033893A586582CE11A6392FF8581A3E5C6D7EF6E9DB08833A043AC0179AECC2505242C84DAAA4AB5C7B4CFAC3E17EC0FBC234E07824AA7333CE7988DE6CB3EDD47CF06FBD9D4264A57D5E4F0CEE8E0747EDB5D18463E1FDBDA91C1D938FBD135B898D1AA3B7E49A2D6BEA6D0DEA07BD614AB017B023798C560F6335316D580E3F5CD95610A0C0E40256D779B3761E4C2DB2422CEAD6B6D7F068BC077CF2E7EF42C6DE30BBE493903BEAE8D8020FCDA5ECAFFB23D956FEEDA8F90F22A17D68706FFE5FFA975865A596D21792FF7C5622CCC88C2EA0BE56B3DF069731B22C9116DA6A9D75AA8CA50EA84F41E8DBC5757D262C8B3FD0F741CEEFB3CD3FF9E0F1B2834C91ED3D15A5B9DAAE04502130C16A67018A6248B37DE47ED0282E1C7133C4D2E626F27A2827A93CBCE1165DD40EF6A5A26E373599D8DFE2C3F775CA41A279D23CBDE07D697DD52462D2C1878FFF629CFBE70051D6846C78ADF01716BB691DEB014361FA9DC313301BE423F410A9BB92455EA8B20485147D2A3C365F51B1186419FD93D6F533A2474B12B829320C5F150A0A9078D9FACEA7CF291DE2B654DA293C98F6153E680375ACCAAE2BD4315F4C6FCE216D4BD6FA8AE38F0A188A6AB7A85CA96A8C17DBB2D97006C47FF07C9EE493BCDFE9D6819F8269696B470AE1CF022E5FFF6241FB55C0EA79589CB07B0437AEDF524D14BAF5F22E864B47782819500AC78EC26597135BB583190F189109338A27D0055791A0DEA5D8261F9A9C26C7DF1F7C02D3B8DC03D4FA67A81C880EBCD8318B37C52198D048BD45A8752310D42229E002EAF0F5C0EA12CC647B10403890D06BB4B47699E3964AD473FE9A0D5C20EE43F6C5C96C10A160DD7C97BB3701FBE3DA9EEFFCF0A510A7FF543A110CADECF6AECF5226134F2807AB2607799395CB4CDB670A3F1F487AF2CE4CA3F0B8EA4D3CFCD65729436C1F24A4344C7D0C1713C0B5529322880AEC64FFB4E3A8B1BD1EA28448571D7F603D0AC30A62910F8005BCD5BC29389DFF962925A666C3DD3D5A05206C251693A6B34BAFAFAAFE8A0BF96FEF695480E9AA332F6ED7F5D0EFB6016F0B593F0EDA2B066FC8AE05E37AB549335A999A26A6976588E3531355AF21A831E9DC31D12805C28D29C8A19AF52CF3C0666C324AF9F6A1CF5650D72BC9A883E7ECD3CB2DF771FD39AF6231B388F0F85966FB60679641FF35528F85373C71167B13AD8216781C3A081649A1E1D792766F30AABEC2BB335EE6A610585CF074CCD43F7808EB0304BD91D2E08EEF33345845CC663BDEDE97159CCC8FC5BDFDE3CA339EA2BEE2850457595E9BA24D7ED422735137F568BCE1B7E6F16D0F94AFB195AD43F1951920001609DBCAB43F4DB6CBB10CDE443018EE611A2BC475BF8AF96684AAB99D24E03E8209EA0E064526FBE616C8C527390E48C81DC2354C7DDE3CD9673EBE57089706E70D3B1EA3F642D83A4FB1A5DC356801B86DC75C24EE218EB2D3ABDF645DDAA1424777EE01052B997ED1D38F2A55FC269E565E8F2FDB674E3F508795584BC599BA5EB3C2E2ECB93839359980000E4774E93436950CEB9A63D50D9456D2C7ABB517904BAB1AC82DFC3DF59A368F326C206EA092B7B0082446391CBD569B27B2556899FC3E8B23080B9F1B1315B1B95A6E27F970072EE89FF3CA4D257B1AAFED3B610410B441BF7DD4F95FCB5B364673281F5AA47781636D39A82A4D8141F90C206E149C0DA5A64C48C3D3248F289C798B7EAB6FDE3015E7E28CA8C44DC89C2F23380057E3403D1300D4E7E8E38545449D10BF0C51E3349AE060081BE4F8D0FDBD0F1924678D037AC5F8AC2EC2576ECEC0EA79589CB07B0437AEDF524D14BAF580AB402C591CA94B316A5D209A6508D2097DC23581CDFA72EA11715CF62460D2D44017D8F04DB3BF574B1B84D4CD5606F12F8D3D1B510C28F8788951B52BC8252E072C363FDD7501072B469952067E65B6795052B572F5600E60CD4CEBAEF82A3914A2E06BE2687EE90D6EF094CE7145BAF8DD868805957C029495953816A29BF7B90DA9FC027927A6505257C3E56293CB587BC4BD594EA26668C06FEB66267633CE7988DE6CB3EDD47CF06FBD9D426479495D471A32AD1AFADC22D5BACD44477591D872D394BE10DC917FF63FF3CC3F5B41EA00EDFF64C150C7531F5634F1360CBCD6BC1611E2CE07C987FED0DE1D454E871BD31E6AF9849779CBB330D871CE21448D2A59F2ACE7EA9548CA988B301D3343B2107D777E332B8ED8BD043D8808010686D809368529AD43586963474551A8999261698C57513E637B5E6EFDABCE21FC7257C6079DF692170E0065674323F710E763EE3A89423B6E0204514440C1434C196A670C732FF338328FF27E712305C28D29C8A19AF52CF3C0666C324AF9EB2E3F88B7413C33F736278C74D89B3C4CFCD8FCEE0DD7AAAE06C3F0AFB7775BE190FA4FBCBB19FD1484602AFEBA5972C38E8B132588FCD6BFB8CB7B8E67F8CD6E9747CB7AD4FF463C6CAB9C3D9AD1A467E88BDFBAE0B4F29F6BBC1861986E0C69181B1086D21429466DE1593B293FAB41EA8E892D050A3878B679562A2FF4C4B48F7AB792C504BA921893AA271F7780657D9141C04C46DB34A34BFA4C47F1F53EBAF8746BC1FCCFFB43F0616ADD0383DC3A6E8B770DFD7CBBA538BCD210914CEB2E799163E163C6A7D79FBAD027291C14C0D5C2ED3D5699C1EA5344DD3A56E64508927480B25B4D36D1ADE3E08B0E5E41FF35528F85373C71167B13AD8216782DE60F6E81CD5E10D3D279E87B8EF46A36B6DC901FF6AAA05763A10334ADF038DDB6C968FC3CEC9BC9CBB9A6F3B2F097906E179EB4FDFD328EDA28B47FCF14E4CA3C8BF264AD455A817857CC7136A0E4518270B935D52B6AB3EE5CE7593CFA4AE1F9E274B9C918F2A9FBD323E83F7A8B0C9340A2D88138AAB98F9A40EFC416D1F37977067C59B4ADD60C2D4343A6DF95DDA0E83B40CED0D291D073D53DDE5B5BB48BFCF8D3A53ABB3FBB27B19D711A547DB695A2B3576C9129C9A29219E8535B5DDF3A71F1B30AB7C933219AC3E9F9263B083A12CD055F55F9B54FDBFF3272AC1B36CC7B3C67DA8A559E42E59D9F92A443AA39F890F6704590130442378F82919DDE5DE7C7AAB44DE9B4E3F5A57B31CBC19C95BC788EA483D88A87B7A0365A49AFEAF09B249945E02557FED97996FBBDAC3FBB12E58FDC2B770756BD0D34466904F612918E104478A44FA5830D4D8D4E8014F570B78B86506571095A78ECE65E60DEADAA9954354B17693D82E87CD6D232A71165A471703EB3DD25285BCC83FE651684DD5ECCDBEA7B530ED7763A68D9008BA197AF7F2061F88E4B30AE4313D735102ABA1C46D78202A0FDB667E0030E75EB6491F5EB653ED8758F003A915C896CA393E0E89C2BD1C4A16615C13F399B90B0EF15B26D742D5A8D9D3C2E5C77A1C5473858B50B0AF63F51DDB1A0C332DFACB228275DEFD66C1DACACCE720ABB62BA538C7FF8C32892939ABF9C659EAEEDE9F6A7053444F4966AE58910458DAF509D2527E3CCF57906FC8AF79561DCCE0721FC7257C6079DF692170E0065674323151C0D90564EBB08D471152227280A72BEE1237DCB03FB5A1DE7C1005789F56593D23AF9467EB995630A2590DF26B7F6B4620A03DFB1328E73FFACFC1DC96592C0EA79589CB07B0437AEDF524D14BAF5F3CFBD702B465312D1385A50893132EC5F609D7A6E3406D1460606A6FAA8E62A38E46BAB9F0645F70447212E94AD982B8512C0682EE2EB6C8AF927CBF3E4FAFB5C582D1D0AC354928FAFF8CFC4A8557474DC19A842D1CAC1A70E5211488813C44A69F14277C3780FD8CBA3636B8DB30B89256CC5F519746BC7D78F7A3712D413D5C9B1C49C1BF7D657A4727AA51C3FA2DCBA2A665EE752EA78C9780925A06A0DD5C9B1C49C1BF7D657A4727AA51C3FA2D6A8225E2D5F3BB74DBBD7EBCAC7D2017E90D85B3C77C0FCC49FE301727799C503D4FA67A81C880EBCD8318B37C521989E084A2A7AA1ED67B4D5B002028E786807A54FD7F2034514A0C3E601335FC3BE39F1F093355C1EBF82539386158134659753E1704E464B4C4DCC79646664FE8E7E0F7FB6D72FFB97550E301A5B2B83928151EA935C11E8CA3A19BDD6BDD8F5C547548D7EDFDDDD7A0985C0083B46010005C28D29C8A19AF52CF3C0666C324AF9AEBE964003215D9B42BBA0235C008E83AAB3017060DA4D3869E70B6F13A4921C52E734C5E0532CC5CC9E4F37F2439E94C13A9942AB30E124CB2406182332A7944BF181CE62614AD5C88B64693131FA91AC587D400978887B1B94DF79F580DAEE39BCB505DF889E8FCDD5156A0BD29FC605C28D29C8A19AF52CF3C0666C324AF9801A6A9362FBF4B57F8929CB0965044A5E812B96D9B5B313ED44171F241A61C04E909771F2A1F84176DDEE101718233A95C0B73D71A39452816FCCD452BC6C4CE601A2CB72C1B7CCF748EC4D882E64A817D729A8A9C8E792CA61A83DABE3CA321E38D1611DB9E0035EDB1C7E7BA812E9E326388BEB807A41ED24A1E06EE33A593B643FD5B2B881BFC21AED48BEB3A8BA15053C7EB28CC4011D4CF66CD8A81B5039EA667BCDF93E8079741F5BF4EDFCCDFF6AB481CB5CBA2601A9A1F172D022D1AF8CD20DC7090EA4852F04E4C282828731E93D4068B8D85621C75C35C27081EA1EE50ED98BB7EFD8E9B7E2072F7E1E4581C33530F536AE0482D898FB005200ED602AE6E6460103BB2FD2999A5738D209FAF98C72961D5F873F8A4634BB1F3BAD24104435C96AACE15780099B6642F3D511CA9064D021FF37C4F90EF5CF75762DEA546B7762117FBAB99E2A92ABE34711FFFC9EE1183059EBEA0A45FCD91681CEE77006425DB8F2F2F499C6840C8D431E4F417E03F6602689BCC51B685C4C8E5BD3BB00D2379EED5B016E970203105654B5A5D8A7A199200C96C845E979332EB05894B4AB36C89E3BF0FED36F97A7A55B44C3E11E54CF6BB7F95C4020076E991B9478175E22A6EE6B67AF3E8291E303DE2C5C93FBC9FA0566623557A2B41FAFBA18AF9A5FB463C9E9C9A70BD7FB0BD16E5B103A734B7BF28BEB0A14A589BD3192C175E5B509EDC382FEF3A6A99302D0DAE3FA1DD5B49E837DCB1826EC9921A19B16CC76876C08296B4EB21E2DDBF49BC0B2A20A027C3C69994ABD2361200FFA8CCFB6A8C6643EEFB73FA2A24F4319FE9205EB689B285F77A25B7946572A4E9C9F09CB871AC2FB4527E3B7B8158C5AA8B997D6CD152BF45A3315C3C2A524865BBCE0542A5CE1DD6A71B1D08F448E5BF4CB920B64CA874FDF1FED718ABF65FFB008436053D1E9B0AD97001EBAE0B87E86AEF14DD5F9AC13F20B0CB2C4F50F7EC22116DE98C45BCE676AF3D6D026B03B39681CB92E94ABD921F99E71BD2BEC222154052A3BD454B5C92C39285F915649A3EA817735AB9ECDA654C49DF5EEA9084B30BE9F3D2D2513D0209C0498BC89D4AD116312D4B446DB811E0239C2ABC783B8FA00FB26A635FF754B76B1C2679862851743E2E5BF86CD44061D5958E05467AD6E4DA3BABC8754A5FAFD26BB892F3CAD2D5540F908C8DBE46737EAFAFE49D195F60CEFFCABEBF4380285B21CC44241D38CA1F3C5BEF4CF3FA66DF25C5EF5CC67737E14366134D0DD9631DF2C5B245E1017EDD0DC8646A873ED364E2404A40EE35DBD1A1FB03093DF6C1D47442F0C5F47518869EEEBD1555A52660E18D06BC7A66EE62A83735EB75A503E0A612504FCCA9A5DEFFFE51F0764EFDB4636D7FCD9B7A3F7408C8F5C974DED30DC7B447D1EC863A218F1FCD54F6DE7339705260205F979650AC27241C4D98DBE075497A204AF9EA62CDC5562E768BDCBA0665F77B1FC3E5F7D9680A5979110B65453E0E5D773B0E6E5200A6636F2E59778403DCBAB7BC43A0F3CDBFE6A21ED6EB5DC617C55FD4819A2EB7C5D7E99D4613F918535DE881E38C7DCA21FD3E84AD5734EC91D72FCB9E9001641089E6274FAAE7806E68E3A86A1C8A5F29540ECA3521A9D3D11AE42B3EF3A67E951A85893F578B8203F555DC683BAC144574E5A9B0B09F7ED0282E1C7133C4D2E626F27A2827A9327BB42185E5330D4D6A22FA30B17313B2B636BD505BA703F3A43F1225D49ED941FF35528F85373C71167B13AD821678AFA0B5A629A0C6F7EE91A057375360ABC0EA79589CB07B0437AEDF524D14BAF53CC11A9D3763AB0ACD845EE72CA3E7418E252CE748F4D11DB71F3283707522C113D2B9B030BCE6E5060EEC5BECE25EFB1E217DE1D2FBA5C925A36C11A45A39264D828C04AB8D65F478CD4DDD9A1B99905456DF0FB6842B1E33FDACF1162402076D62551DE6477CFDEFA748F54F49B6C58A0A8B7A925B9F6D2EDCA5F70CCA9446694F4F2A70CE82075B89078AE10E3083622D9EC658712AB9F8F98EE0919783C121FC7257C6079DF692170E0065674323130AFEA5E8DC8C4B3119B319A83E817B85F756DBBF3E3FDB925C2628F2294D38FBA107752E8609C83B3BB3A67E77A769B56F17EA3D468E0BC78A7D47B82C676C05C28D29C8A19AF52CF3C0666C324AF99C5BCB71071114010CE5500B358BF4CFC0EA79589CB07B0437AEDF524D14BAF5D602520B5C3CEFEBED377C19F7C2AF3B13CDB7A1959747F253545FC81C19CC327FBADC0C930ACE112A5173B116D438CE66248B7DDB757E7A1792787F77357CE3046EB305F533ECD50C813AE29E2608A748C81DC2354C7DDE3CD9673EBE570897493CF9E38A4E3DE62AB18B1A692FC19A24067C1CDE5F420437CEE20C18CF40E18CEB5957C2F46F11AE115A92C708BCAD15DBCB43D375A62B25532434C9CB797207455BA4517EB8A4254F2B2A0C54F8CEC8BFEE2036E9B1CBA72037B8AB699BB72BD4D322123D437B2A4C5341A9ED7B89EC91D72FCB9E9001641089E6274FAAE7206E61EA76C697795B53E14D1830F0EADD8F4A924BDEBE254DD7288F66C43BE7BC76F1471096AD3AA6980CBD2B0154E146C990CA0B52C562AD7CA21AD936BD5A32348CFB29058CBFEA91C9BAAA09B54334A9D2206858A323EE752BA209BF075E1BE9E92DD43DA126704227AD6C8ACEC9C07D0167CC59994D73253E78BC50327EE2383A0D49B7CE1810102876625151FCFF0C4B8263FC912299D0593B924639F4ADF3F280A832B20B79BE51F51781010C833E21F464BF3C84CD5B8AA17B0DB6846F92E92013193A7192776D562A15740FF046B622351C2FD5FB1ED9C3A84AB4F49C9DF4A3E47947E36464863DBCE729586EBBA0DAE7282DE6459936D55336BD0110E5EBAB11167188A455736035747A94792F41582B1A13DBF325CE1451C30DF141FF35528F85373C71167B13AD821678773B0B220363D11B26EC7FF0CA206252BDCF073ADF9C1F7C69BF5E2CF9D088E147AC62094699DDB977F08722CC1D1A471064B1FDF47E1B6B15F1289ABA62F62057D87A0998745AFF3D9AD7C6A05605D3DE39E5FAE2BB95A3C84943A78945B855168E68CFC94B64A7EB1B698B3CFFF5DEA189FAACB6CCDA183D4DE30F4094CEF3582126877B4DE65BFAC092CC12B2BAEDB2C31D10703E570AFBE62578528A27859C7DE619BDF17733F7A80FD26D9861621EB27FA30B79A7F66B146F3090B4F42E8E65C181182E4C57E7DE6A6979158016C933A7A8BC571FDB93463510B0295DC9D633FCDCC6639DD8C1B60837CFFCB39916D0D7F23CB8FC26E2DC26FDC34572154C932B56BE7C884321BE1D37852064E1DC863053C0D1247C3AB4633D7D51A1DE50CB505AC71D99C1D57B2437FCE495BA0A078CE9F9CAFE2E946A2BAB340A1C5B1591C199D28D761CD11803413555796599BAC31C61E20732898D50C2A1F4A818B79DA78175FB9DADE105FFB7AB95A2059DDC4ABAD841EC39B5C80AD70F43515BD9756F809DB3CFEF7E0F85F0D50D2B54DC9A1149C68DB091A5F5B6B8F4F8CDDBC2EE58A460D826E8032AB03360C03C21F10D3BAACC2F83C4DA443D6D6FEDCF2D95391FCF84C681E5342E974FA0824112B122182B86062A7239E2CA146FD1D0887EC0236A96EAFA216962E780AA905967E1F72A485661B990DA62CD535E64E6B17176DEF76C248B1DDF33A991D7927E62348CDE86AD987CA06D9D87110CDCCB3108D374DCD493C4D25B060E21B6353B561A6C970543D9DDBF94C3225E91C11AE157E502C3DF2D4E693B54D0F99C8F11EEEDE8B6DC427EEF408C0B35C8D6A48B1366F1D54BC29AFDA8296AD7B828C3813496CB0A7C094F05FC1EC5B0D4789D4917CBB0518571533B17C89D4D501B7DB15ADEC2099E66CCC7290B7676DBC08435F622C1518289042663C95E83099DFB0AEBD2A094759CFA6B924E9645F407AF56D95F0168522F92379D4A235D9274B128D5D20BEA2E33AA199A26BE33692438F35AE3E4887596A7B21A4F46286E5F94F4A829450014225B9D5B18536A8042D3EB9FDEC68EC2177001A10DCDC94D1D047F4138E9ECE19F179BC105FE39A4DBE86B4564878A04083C82C5B91181540A0AE1DCFB1B92289D8AC15FBB504360E6136FD7924242964139BB14D59D25A0B999930E0D30990D98376ECF101E3510E2DD605A64A7301804DDB1454D479D208A4D5929F311FDA8D7E8F0A38FCE7AC22AA98524138EEBFD64049F667EC1404388EFD6F760862C71EB223A18634BBB0D3147254041C640F1037FC5EE323B8F9C6BA18C16D7A1267C6D9DF871F377DF4A8783D3FB3519A06E0467A9FEBF999122E7693B82015B9A915EAC60C4A4F5D8112EF27C3AC9DF4AF759AD04E0ACFC3F9D793634F295391FCF84C681E5342E974FA0824112F709FAE6142DA20DD0AF802C36A45FF0CFEE0D0C299526927E2569D98EA2FC6D5F0168522F92379D4A235D9274B128D5D20BEA2E33AA199A26BE33692438F35AE3E4887596A7B21A4F46286E5F94F4A8E01FB5F41E32ACFA14B1AF7263FC1E172BE960574941FE14256E0748B0C6EB49D21AD9286E1A610C27677305D31B34252016F50620496A77111828928368584361FF012011CC89EEEF5A47D58144A524665A9DFECB48F654E8B8CB66C0F9323B8B396BC7B0C10ED0FCFEC94883A6F0072F2E6ACD78A2BDAAAD627DFCE7CC78AF457DC2CD01884CEA34DAF8F95D67665AF1D899A3E0B2616ADDCEEF8D29307CD057A6DF8E43779824CBAB01AD16FD96F5B1EF471D2FB5C53388F5951A07E0C18A3251394491DEE06996C196DF89A50B459B9C7587381A2CBFA766156A174736DBF2BA82DBDF197C6AF6BEC5D28ECE2EBDA283E5429EBD694D5CB787BD47D42F6AD3145F0D50F7617D77AFB8035AE4953E35CC60AFA48D9F06088912135461AE8CD43012DD69F11637827590E2125B808AE5E458F1475C71406F28D5428A648824B9547B082C0BCFDF2D157289B32078D39CCBACEB8F28D3430FC5C9ACA1926AD60737595D7AA2F30B59C790F2C3114BCF7E2E6DEAECA3C502FBF573DFC856F3DE8423F21CAC9285172F85A6F44FD75529A8DF30E57BF156B9C4B7AC614438A312886E8AF5A63B297F390D5B217903B24DCF15FCA0E9BEE5766791688315522AB6F11C7B947B8F6F8670C05D2635C31C9635C7891C322ED398C0FFE0D2344E06280AF0A0FE97DC1A63021722B5D4D5A6365F0D0733E3CF94E5784561C9708FA804F588322031662A1D993FBD2B7CB0F2CF7FF6BA1C4D0C86EF58D66912C617139AA44ADA01E266EDB2C8B9D0A25643A5F526413FAB629DF09AAFC836A2FD56227428F2E7085188F8962CF391E4CD265523D6E89D4F131DC43A5212CBF58B0A80BF65D84FE5D9759862C753E63356EAEDB2676A8470F61040C40B5E6BBCA6F01FBE0851A892C409ADFAD3097B6F7F631F84B1A006400F30AE24D888468A9867BCD3E4C07D51BEDB6A52F5B86F054F60DF061BC63C43BA6176E4A5B02E6E669D46189404B709C2D37D4D92213149E02A3B285B29E41D5CE24ACA35CBA3E93831A6E75104374369AFF531C4B5B7405E711EFFFCEADCE1F1CCA5C7AD3E4CC89D2581CE12907EB337AD8CE4E54CB222C89C20F539F1161485793B5807B441045BA843C007FBD664A153E764C7DC242448719B2BB202D98A180302D1B083DC08E12C94C7AF80A13D0ACC2F98260408651DF7239F286D5C5ACDA1A5EBF78F016A5A7221F226561971E33A5F20556D04E41354024427146B00281B54223491855304A4AC1173999CB9E8818BCB8EE4E2D9DEAA51E495EF44AAC1637A6B45D199B5C9A45EAB8B83C4B374BD432BEAA545ECD974971F12304DBBC1C8823164996F030AF245BAD586C958657ECEA362BBA51C384D99E3B43F24AD84697F965D0E5EFB22AAF36241C7773724DAC4CDB77F4E6C87D2386D6DFFC552DA47A0FB32F5342BC4A0294AA9066E291451F6D7A6CC428BDABE898428A29223AD003EEB642978AAB9D8DE9808970B442121831D04356C3161E8251B13BD6F6D0ED50E2440C7A59B3FAE513B6FA16D7345E17FAAF1A381D0ED1A7754BDED1F58EDE6989E7F9DA13B8CA136C50BD36638B3C3BBAA81F883885F5449E3D595B9CB309D5D607C37A91D26B09884104443917ABE1C8255DD34A77FAA3B746C038EAB5FD3C5499836A35D7D1550666E02CC4D9317680854094E107C71D961E2586F64C1599B2026C0C67C9DDF38B6CF5D4E6FA8A381F652743316BAE79062E2380DC916B7F0496A4F67A9655E7A7290D3296E619B14B370291B7A2AB2B3295260205F979650AC27241C4D98DBE075497A204AF9EA62CDC5562E768BDCBA06FA1803CC024FF9295EB3A7DA79B4F0ED83466F8CECCBB40621708BA729FE75376A7B53C2B1E001E3BE5047DC553B7405DA146FB10ACFC73697C9C4C7018A19583B1557093DD466B8AAF1F8B5058165779EAEA59BE175C862B83B87264990C039A3B77D8B19E3A6CC22C70087E90C1ADB3B51B1664870371DC8207A3E155B1836E626F6BD4395121CEE9D11BD9160BF221F80BA0C6F0DA424022893A1B583C7088C0B261CA36E37B40B84540B4DFC25E55D8C2E8C6E03B5D487CF09662503394EB700FAA44529B9C30E9419A2E26BD6B2E676A36A9A8EA37E580F95CCD4A38B4E651CE9690794F463913732718B569A8501B35FC0E90CDCCE1724507AD13D5ED7F07CC41D1BEFD1598FDDCC83F37980544E360A3FF34F0B08CA1184B3FF5219A04C43E9B68782F9594758F49A6F3D342B0D812C1B13A620343F15D9ACE0022E33BA499744DA27F277F914D56DE45698A741FF35528F85373C71167B13AD82167834B08BF720F2414C19F67E427EBD2D4D14CC2ACB24F7F5F8FE32B35982FC5E43030188027E824EA2910708DECAA47ECFAED42413FEA5137E8257CC0D4E949B116C89784C4BC37F5B02614941CED6639676541AF4DDF436980BB7693C813E6A9C44D6FBDC48E2B9A40AF1EE67463180D4705709E78F7FE799EF00ADAD414F83FA3BCF391E82F3C26B90520C4DA669ABF7DFBFEA315006F450397CFAE72EB9BD2B41FF35528F85373C71167B13AD821678F3E62A2C6306C92EFF436518FEB2B887130AFEA5E8DC8C4B3119B319A83E817BC1AB833B969E351C113114E5AEC7982B45FA2222959403E2B2FD6BDD4A56D2A4E9568BC1830E91577F2683A424773DA230505E197890167509F51FEAC10D63A3DE33CE61E2834CCDE976EA39B9C9CEF30751FDD33271D05743FEFC045A975D0205C28D29C8A19AF52CF3C0666C324AF9606C3600C5BA9C05735C636212AE00EE05C28D29C8A19AF52CF3C0666C324AF976C444D691CC6363DF8F5E1ACC4DEB90C3C84BB5C3DA8054A0CDC1CAA68614ACDE33CE61E2834CCDE976EA39B9C9CEF3F6D0C7D315C24F7007700992215EE31C0E5FD4E6C8B7DC718A3F26D468931CB9EDA4202C51A8100878F0259D6E78D6370C9340A2D88138AAB98F9A40EFC416D165A9099F3D8C2E642328B84175E18524DB2F5136BDFC63A5C442B4EB71D67074DA31D80A6AEB5A00155C73FAB3B374888A08063F24539FCD1579DAAC2A6DF8BA36B6DC901FF6AAA05763A10334ADF038EFD40ECBE72A1BFB9CE8AA8774DD4B1F21E2F1B6CB1966010B97E2265B473A29B38D3406B97830B6FCD9410DC3FBF7BE63937B0129B949454EE9EBEC761CF05ECE5C9A603709C2BBC9561D22AAB0827C5C800BF685ACAB3A15FF9EF4627B5A045FFE3C7A552B8FE7458E4D4A55F66F531686B69A0E65F120FFBAF1D63A65619DCBEAC05C344A9770422194F87CB362906887515FB87A9CA56CD78E1D860C8E5C49DC50567382E2AFDF9D9F2698FEAD057725DF33429580C2960646228417535575DE843733BFDBB31CD65842DD19DC82F1835E2BA29F02049756FB11606EE7BA393D385150A4E2A381CA50EF60E503DE0E7D3CD8AC1118872321C99BCBEDC9C1317C96F91A8437983D36E782336B61FC36AC68825B1D1DE26C17E4B98F8549E206AA207DFCF13B366F018F1975ACED82EFC606F9904B45FBE22C4483A243C2F3CC533ED020D9F2D5D8DE507E9AC031BEF72A03C4D5FADB162B4BC0D0348CB30CA16AAC4A7E287D7C407373AC0FD88B66C6E0FDE0B9BE374900FDCB68DC218F755659A7038813EED7479A1FE0337BF067EDDED5D58FC3F762650B152499C57499447F4B494815F722116584213F98D4846D7E934B259916E66453C3CE956B7D9DCA92879312BEEEB5BDA5A45E9F7444250EBD8982591D5CD660E22D5F46972CC19DF2799F369EF0535CA58CA18F79BAF5C60EDC77819352C12053B113AE113D95750E232C756DD91DB44FDD225C2F1DEAE14DB82FA214F3094E2B91B1AE2EF8B5F71DE619AF867CA6223135F2B596C406D22C4C626DC15300BE20052DE1E49E61204862D0814D2A7F145C778039BF3CCD6F6C5F739F073A0313C72284411D65A28A08063F24539FCD1579DAAC2A6DF8BA36B6DC901FF6AAA05763A10334ADF0385D020126A63F933CF3B92F5C797A127821FC7257C6079DF692170E0065674323A086F526DD55CBE899B1DEC4E53DC71B11A08C2726D1B3575B2278203F56348316F4CCC3EC7CF487AE61DB84F935E878B05313C3F0613C5BEE7E2E566567353A5F25648F5E15CE28B3A1B9C6E3E2996295391FCF84C681E5342E974FA0824112C4D500B99DC2C206A62CB88EB33395B774EAC744B0C62097D8D50F43FCFCFC41DE33CE61E2834CCDE976EA39B9C9CEF3EC58EB2C60384394FBD3FD23B138E8A0BA526F6F4C1964F83C03951BC8FEBB788660015823BB51255EC74DC1974A29126E9747CB7AD4FF463C6CAB9C3D9AD1A4AC74610134768DCE49DD211F29129D57B0104B4F52ECA43EC8A4E54F09894973CF7A0F958EB82E785FD669E0D0723F1A954DF1605DF2EA6EB30A9E3C7E5853AF169AA32069728E4095D817E6BA020DA2321047D5516EECB6DE17E17F47819B3D5E1B9D5B07889AD00A8CAD9F86CD5753A6D49D45FBAE46B936E892A12B162A1278D0E675578B8FCCD3762E1CC97649EE81E07CCB0E02F5743CD9454618F6244628BEB08C56DC781560F27E90641217BFF41975CDD8F88180FB6749B364D47B7BD9064B4F85F2773BBA45DC865A9D32CFA2A470527A6497EADBD968068D02FE57336FB7E8CB29DB344343255DD767E4BB846CEFBED4266EFBCED105319701EF66D1AB82ADBA18220DEF216EB52D909D7C8213BFCFE93D437F89B5CF0D4E621AD15F25648F5E15CE28B3A1B9C6E3E2996240ED516BA9AEEF92ADB29F3A7A69B70D05FD3D7D38193717FA603738A8BCA29E874BADE9CE6DB4E54DC7CBDBA5E1B390DE1AEDC5C49478E0D029291F3A98D3154F25CBF61558AF773958E31D38F11B321B7301CF4A26B6C5D30EFDE6C5FE5D89C0EA79589CB07B0437AEDF524D14BAF556005612042EFEE55F230EE97B65725F2271413966089C1BBA0304E7D6A475C3DF9A626AB43D0CEABE5945B40BAFBEC9AB26A8367BCA6FE12782EAB8DF36D3859061B9C14F3690A5186B07C0AE5D73C9080B431BEEA1D327767A180F2AA72F2A6104B2311E7A841115EB885BD0723319CA8A9A487744D6945EF812978B3A7E73A325B22744F920EA44DB0256F6DEB659A08A4CF56135622370C8A1E9381601956A4C2383D8242B3437E4765C62594E2FBF52571A07CEC204879590B7BAB7FDE405C28D29C8A19AF52CF3C0666C324AF9FD3CE85280A2D758C01B7D8CE889B8C2C5082D3F0D2E4FB1255E093D2FC7B81F692AB419C5AA1D32A030CDB7DDFCB256DA4F3A5E3D890E785195DB37FC0516B4111C47EFA76539A16E198877A26C5D3DFA000787AA7191C3311B4F1E7328B32E0D5D1406B4647450A8D69D26431D184DC3931792B68CB5F619AF5E45AB94CF3EB9B628AAE240F71D8F81942615A62618DBDCF3290826EE9B97FC3AC3B23BA5FC7961F38BED9E636873621A9FFB90A50784AD1CEE38CFFCB9AAD3E6F7BD78DC75143A5CA235CAD1106F80B91D43A669C5DC95704ED70EE462BBFD35957AFC94DEA876A3096C23A2E08DB4619B5C48DB7F3FEAC8DEBD60642424511DC26DCE6B744EAA34F4D0FFEDCB4C8A1AD244FEEF54C1AB833B969E351C113114E5AEC7982BC2AC15EDF8771A6077FCDAE3526187AF2CA7927920D77B85197B8E1D302B7BBE3E0531BD69AB22A4A19D51F7C795668FDCFFF5B5865360B6423275EEA9CD092462F87F8E1103E18228E314AAA7C3290BAE6A688DE97740003190CAF86A630BE75751EF136C99509A91A6BDAC5BD970B598876D4BB387E241CF866159815B4578F1575685BCB7722E3B87E7B934B75344599AF72259507AA1B3CCF43DD82248BBA22082958DE6E9FBF7B1A3F9D4A4058DC1AB833B969E351C113114E5AEC7982B8671DAF4A2D18DD92C974D2356C611D605C28D29C8A19AF52CF3C0666C324AF9A3D4630228F96E7B25D0ACAFF6A6ECFDB75F97F8C7A81155C2956FC44E079E545DF647E3985E9C1151D134F0E6350073E06B5A60EDFE80A70E8B1222310C7CFC1A639AE85B45F3836BB04BAB585541EB2534BE065AC456A902904463FF609EFDE674436BE5D8BA29198F3E2E9C905569D131EB3A99CBF5B66270D1FDB90B1642653E21E9863AD5BB6D94BFA52C86696A3DC10C1F9E880E5A2BF4840DD77A5BB0D4C5472495C369FE7E96358A2FD4AEE2B079A652121D68B59E09078BC05D425F704B0BFA9579F877FB82CDF89505C18E111A3E63D3FDF2060D6D6583FF6F251CA7C65AE869512237F68A70FE61FCFDF762A5478716F27529D07481C9F55C9A09C7BA845CC367D7959D4BD76341D4132C91C827DBE187AB866B6AA49C6C2882F20F328A1C2B5B06BE47B9A310CBFDDEEC5075C8FD9A15796E0033893A586582CE345C01AFBA363CA5E13C9FF0A357E0F5FC7D14E0F11CADEC794EBF788210252643A665395B5E11ED9E6096A49E6C943776BBB2B4F211FC11560C311EC44D4C2EC454266A1F9077EF38EEFA2C3F00B34B25914A671D127426AB198B310C858CCCFA621259B75DA53577AE2E9A6752348EC0EA79589CB07B0437AEDF524D14BAF55EFBEBFE72B69CE04E1C72A52D3F621CBBAE44321C30D3320A3FD5672E3B2E062BEC43AC9FE9810C1BA1790C0F0FFBECC0EA79589CB07B0437AEDF524D14BAF5F1575685BCB7722E3B87E7B934B75344E318B6BB4703C76D2723767BBAB22098AE2BE03B82A583CB6A66A8442663AEFB05C28D29C8A19AF52CF3C0666C324AF992EADA4C79EFE9BEF1273F0458E398A5E85FD04E7C7A67588A9345E8B43098FA835CECB4804B953B65800B2D34B0954D152D19F241CC50800094FABC437A7250FD2253D3129C1B32D8B45EBC7F1FADCB5F25648F5E15CE28B3A1B9C6E3E2996220200FE661CD3ABBA9891AFC80F217CDD640082B8077A1E4AEF8D023F91B5BC63F31D4D52077EF3EA6AE62786396548866B350772F6CB61A98188F869F21A144A7CF291DE2B654DA293C98F6153E6803EDA4202C51A8100878F0259D6E78D637545421AF73DF114174D1B0CDAA710C0D4671D9936E9CD93307F8F879CB2CF3C50A1A7BC177FD4D38ADF97ACB09576F2BD91EF1D4A4244F665BD0969D372749A305C28D29C8A19AF52CF3C0666C324AF9A754E6E19DDBB9F189686F1F35C34F0EA7CF291DE2B654DA293C98F6153E680311F71EC57235C517F3522784A6C0F8E1BCE10A712970838484F7BDF1C21ED3DE7CF40C8501C0585F274999EB132F9148C0EA79589CB07B0437AEDF524D14BAF5BACA99811B5E905C8C61EE4A94E212A40C19BA269A4B8F14765A30030B7892C18203F555DC683BAC144574E5A9B0B09F7ED0282E1C7133C4D2E626F27A2827A9285F8935E3588D61BA19C7C429F66A771CEC73C324FC53CC73542B5EF06719779D321F8B0DED862E02AA603521C2AC4947AEA081E2B1F68D73DBADF72DE65C97C1AB833B969E351C113114E5AEC7982BE9B96C30B931E8648050DDE9E16A53B48596249E2869DA7C56246EE74C5719C5BEEC6067020C3EC17CDC8E8953B66F6EC1AB833B969E351C113114E5AEC7982B27EA0E176BF6D98E156E907826154E5B790AB955204CEF0A527C6C705CCC7CF80C9340A2D88138AAB98F9A40EFC416D1CDDFEF3D4F3F0C82F6D09267FBD21C7B929D02085F5EC0D83A0E651C3863CCB3035D8CC452B1884E60C9C58AB79AE0537E103689FBF7184F547BECD5AD25C635FA921FAC0ACC9903B448812BBDF67DCE05C28D29C8A19AF52CF3C0666C324AF9B9EE558BCD2C3970430837EC85D734B87F95E78BF6A283BA9988B521A47A41BDD9C0EC00939DEE2574F7875B3B098A408873C10ECE16421EB807A62B13FCAE375BB74D628AECA3BD9C19C2A8D50946E8ECE0CA25A2FC51283E43C0DE5A9875BDC04E908E0C7902E6AAE0A5B0D65E5FDF1C08146E6E6AEDB3BAA00D3AC5B9DE05232FFB1AFED78C4276558BB008B6C6D9AFBFC143E524263DF4D1BB5D5777F3AF41FF35528F85373C71167B13AD821678F83137E352B0FFEF259E50E333C633E78E7385148F1AAC58BBE4F90A0C948003C3F5E82C58C968A4210D09F2CC641BD2AA332F6ED7F5D0EFB6016F0B593F0EDA42F3BC800C4A5BE02F7D79C637195077C0AC19B39F674ABFC6045E6BA6FF3ACEB3DE1BC658F2F3FD502A8FA21FB1D9425FE1AD724AEE24B5B109BEE072659D0F04DEC773F47D102E132259B565131930F80C3EE1ECB1F16D087A491490472FB5E585E1DDE753D745B4A310655470E695E816E7AB922962924F2CDB0755AEB923A4AFAA64E8BA1176C1A96C3DFE3AFAEC2E3DE96005A0EFF6A7550C4A9B19E509DD295598454DF0B0EE35C1597F8E58015142824F1862C2AAF25D8D62C94DD48231029748140F00642435198C33FDD941129186904549B4CDBBDDDED5A93AEDEA3C2BBD0E9F231ABC8F3B038ACA0BA20F839458E3498653DA238E25A10DC152360095D9A524CE121ED9C0CAC42E76B0390B22E243567E4B7CDDF1E1FBDA6036361F7DB11E6ABD76113536595937372DA9ED714F146CF900DA05140D3094A383EC13021C8409480180F61F473429A59AD961CB10FE4C1C71311C8AF77448BD550F4BA4505ACD43CE1A46D1831B22B78940F652A8D0C5DD7B9F55D0ADB8F9F77770487B016CBA338DFC8FD79B792F67F43E7B67343A19CED970E3BDB606BA72FCE627E8E47911A1BE129811EA9A958EF6952FA7D78A12DCB98FD7978991837D72A16122CE0182D22002982AE940C2022A35356C731CC5D511B6F199BC1E49F783D64879846E5301180138DF9B54285F8BF636D1F431813E6E5D3E0E7C3B63A922FE1838B26A744E21A049512F991A00A41D2ECCE85FAF34D5FD2194A54D12563A4FE31420B9C4228FA379EC22B7AF8214686A2F3B16E93BD32ADDE9DE899FFEBC494C6381F6ADF39A8C2C4F2EA000F9276D5F32D2F263BCD7C1E547C9F386DDDC6407DAA7C52F14A18C57B1F87CF0FDCA5FFBFD6E9D07BE8E82C4D75D9519824200FEC8667ABB0B63AC4D10060FE925B6000364DF0E30FB4F1FC4237FE8679807954921EB72F1BC84B5BDB65F6BF9BB7D74BA3AE6319FB682F26C3A91791DB1B1CF9F33517D61C766E41FF30DD64385DD5379BEDFA7BF0A04328513472DDF52003518676D425B9F932692150E43D5778DE2D6A54D2256C5ADF872B516C41354EFB82DBDC1812C561A499BF951FDF94A5C87B137947231B9A9543E5356CDF43BA11D78F59FEF29F4B474D859BFBF16C0ACE0D0E51F9132BBB98D0C69EE385D6E45792501B80BC6C0B01626DFF645304DA433A37D311BC70BF53E0AF1AEF81963E6A3CE7B2E4450B87B01C84B0BDD220597EE8B17DD072CD2E4FCB995CD4E6E5AE54FF80E939E6E40241DC7B93E2C118C3DDBC9045634743B298AA00862A4EB4188429659382845A813E9D4B9BFDA2D4B16D24DF0E39AF7F399F27B25F3B8612228F8B12050C844D5F2720F28CED22FA9E5161C02EF2F373CB45A1383B09E05B11DB86CF1C3B662D9D6C023EA07FE6F2C2C6946C23EC9A45A066AD3D184B8D5D500D846EDD46C8C61710B5817C576EDAC0453DA8BFDB759955DEB87E6A5F75D95AB0B43EF8F0848242966234CF315C1951E91B2C55D1EDBE3EC1419AEFE2347C59D649E14035D9899B29F14AA6085C5CFDA25BAE52F86F4D1BD4556DEA43CCDC0A944AC903F2F89A3FE4D229AE349B5509B140FEF64826092BB018CE69EB7685AF2A6A57665488FC4A5B6B5A0D61A7948FE41675E1B1EF2E090C9E162284635F3F9E412C1C846996B909917F121FE4FEE786083B60A40E74ACF84D7753B5B38E328A98203F555DC683BAC144574E5A9B0B09F7ED0282E1C7133C4D2E626F27A2827A960129571A954CBF4581E484C9D4B7540826FCD5378B557CBC1EBF7C22EB86318B3A58E460D8CEA593F35575BAA35B60EE27C6E3512E8D648B6D80ED3DB9166F4FF4AEC892B61EABFAE1527066FA081D8689C05803406FAFBC563D7B7D50344964B3A96FA3AA3DCB8986FC9FE7154242E65FA35B99852A1BFF4B4487D2ADEC6A62059891A700283696F8175F1D805F148390BA30F9A7B2EC8F0A29654E82C3EC3035A77040D192FF717D9692446F873FCC058DF1A77D1C460094501F243A53CA0457FDEB92D231B67F5BC2F348BD7BE75CBEAC05C344A9770422194F87CB36290ED3A6D5C2F793D82F9EADC940F1868AF4B5D40E4AE44F7BEFD5E8551763945A19CB734FD19C7C33F1E1E587C89CC326A9D577ED83AF8AA358F08BA8740C4A7607E6309879257BE3C28F7E455E54BAC25F2A750C4EF89370BF3D22C1C268A5658AD549EBB3D500FDB5EFB67CC1C0E0179B2A92487C83406684D110A05BA7E42B8F0670DFED89AE3175BDEDE6285A47156470C276F611DB5C5485AE19F2E5BDD0F7AE4C7D7E9B9DC1A20255D5AE205937A0C0AAD448D018590D2D73773D2B07652889446CB4110CF3BA8A67660EC52DBA3ACF77AD8CB61197FB327C50969A4BAE7E1C567E4A2E101791ED2FB4A1594DA62A2DAF059A79218BFA512E063E39B06869118CE92EB2DB978402D9840AEAAA0815F46F913F80D8ACAFE4077F09C7A86B8A9F95AFA2FA234B494F0BDBE62EB275F9118CE92EB2DB978402D9840AEAAA0817D7B82E550B2AAF97414B9371612C339D2FC624B8446E010155228FCB6F013741692C99E51783B6A4C74E9468B3D22C8532A8D2E89B718C467E54B152401E3AE3D48CCB08722394F6C3F92FA387BB30CDE566D5B0C654CAAD9094D2997D38707DBA1F34D2E86B76E665F725FDC445E7CF8E68FAE2CBA5A887993189FD5024A91BD83A679A01DF8A425CF9FE85DCD7DB3EE6FB2A067A8FDB88E937BE5269206ACBED470AC6AA8591EC72AF84EB82BA6957C509F3BC280C9D4F6E18633433BBB0EAE3C23C01FB9626BFCC0B93A45D7EA6AFE9F5EC69F089746290BCDC6B889079474DF112181B090EFB0CBAD3B5874AB76FD5D7F550A784090FAC8841D464CEE04877FAD49E75193D04DB983A954ABB64ECD8EA20C8C5B0CA47C95B79C876E52C9A63A5A6E42734CF22E3FB62F5EFBF7491C0CDE6484F537D898D95D108A4A0AF9AA3CCCD0CE01369D8E5F15757854BDA1E1C8B2F7B06D339FB38718653D352F6A9AD791A057BDBD2FACFB606CD8F1B6BBECA6E384E67956633FD30C1153715EABFD983FF6ADD0B29B311E83F05E311066FEB942A9D92DB2B6466463C4951873C5B98DDCFFF96DE3FD7E6FF153ED1107301751BA0CA9B92D66D68A4C209048AC688565EA5538EB8A850EFBB2116C3D13D4C3D44D60451BE14E6E2D47BD24F25C3C4F32E5BD830545DAAD67278E0BD151982BC25C6585181290C7CB686D101B55D4C36226021EA0B7D19D671C70150878FD9086795329C771A6B5202C76BABDD3E373939AEE46D891AC09A4885284685A90796B1E3DA316321E73B2C84BABDE10E7930322F8E5826F8C190CA0E2C26AB733D4EA70BCDCAC0CC5F3E3480BB20CBE7BCC307BDDA0201A71C4C2C8FDCECD16C33DCE3A7663DD5E7A71BD024C5507CB14AD70F9A184E23DF02E222B579A21F51D3FDD9CD220F279074BD409DC74250CFD036EDDC7CC22156BBE4E9F2A39097E2171C084AD4D8CD202A133486BA5105C162059891A700283696F8175F1D805F148710291134431D15702D6861338DD5A6E7F91702EDA2B7D88D736EA3B5CD9599A0DE6A2F200F050CF0880C9D092EFD9334BDDA3913B7D8D2E724AB88EE96B97B3C241D19091565023D5BB654E89598D61DB1B3F94DE628CF5D4CD058E8602C8B12DF0EAF355304C0DEAA4D6C34F9F2C7F5A0587E3AD957AE120B0E4C30AC434ED7E2E6DEAECA3C502FBF573DFC856F3DE2C4155681AF334978090535ED8EEF66A9979F5B7227493EB335B836B5DDB0A332D849963ADFAF82F4FEAFA747F89ED33BA7248E099E474F13ABE4F7355E3661D2FE30ED31C4FBA9C5448D026121B4733ED1304AC60F124F372971A9407A37E3CE2B9A1EEBCE1A42F9E02E0CB1BCFA2F8AA507EACA8847D09BB64B30AB86F5E7A2830A50B50AF20DBCD0370F9286B942769E51B324B3D1963163D6576740F5021BFE081309746B8CE8721265CEB9267B6A6077121FB3B38CFBB908C39C8B40A0C493BE661FD95658909AF6B825053470BB45526B10EE3245936BA4E4276A8E2A01DE832D59AEEF3D84712F4B90F0632AB5BA0652B25D49A308FB06C29AE1F3F3F7F4F3894A81E0B3CD487F68D56ED3844CD2FCC9D208E0B7CF03FB95C09DBCDB1CF7C727A6332F5D3A41C7BF7EE1D267F60FD900211B4737A7345EE80003AF8B5843882AF006A91DC9EDB22170639012F0B9FBE87408EE4EFD94E256AE44B178F00971569FA2445428616D7701F4F303A803C0DF98B6313D3A376085111AD6BE9F98965481DEBF4CDBE81A662557E462F4B8BB7B95FDA36BCBEA490DD7BEC2D8E4C2E4FF0F500B34D82849155347790A763A0262EB5C0B10288B020241255290CBC39EE01D3C8D108F59BB936BDD8F2A82DA5689157FA44BA9B6B302169EE573E66D93654419EAED3CD626905DD77F178AC394BEEDF43723F3CC60E77827AFAFC7CCD8450B6E7A680CB3E2CFFE05B7D5B5B039EC9D39405030052F78AF68972E168923302197F5C1313A29022991707EECDFD5245988934A50FD769BA2FC9C2018374ADB90888155BDA52F280D1C19ED98D0DF197EA99BF15EED8A270C8668A145C2E9B7F5B8D6D3F9E07908519CBB1F58FC0B4E00E1348F69CD4FCAB62585B43587707B28725A48EF5AD8B35ADDFBE0C2E84CEDC518D0F9A9FB24FBE68CC296CB93C5408837934DD9D492D989DCC669563DA0B757A5A0DF78C4D42DC18D76BFC35207CDBD322270493FAD01D50021678E251390A364FDF03FBE0CD9C9FEF5C161EE58854E27E51924083FC38C06C45BE0ED27345373646FF28992121329C857534D74B014352E95060B8EBD533D2F2D14C33FA2288C8286358689FD4989B089EBA04B8AB0CDD1EEBD3B477B0E39CD761F16FBBCB7CF4A9D83A73D6ADEFAC1B3CE386DB4B71E438D8E9662C0F9BB4B0AB6A132CBAF3C9B6EC08DC62C37CF2150F8A1A1283A8C40396AA935270DC868A51CC5580D5064F293B939C203AA4F82EB7C8B744F091D68223EC42584CADEC7D6B41CEC0910AC4B7D61546F4B24825AD32254F1B48EDEDCD762D9BF980BCB6E4012D8ADFFE29A6839BF18557C7FE4E5A28D3DEA7CCCCAF48246E29D3CDFACF584226AC8639318B98CFCB577B67247FB9496FE6128BF388AB5F8E4869E70110469D297C5194A32A22CD9007F6CF269EE5AEB92F1D462D72D26F9FBB4AA7FA914D5E4998614C605F3CA8D3DA86D8E4F86228A469929EC20F5CF8E82EDB8858550581942BF3BA3096107E41FB66EFF45EC3FE5708D5B2DF000530A80E72E8FAFB4DC0CB41CD2892539EBF11C7CCE918CD2529CF35A47AC3F384BF3D0ADC7CBA41725F802D3B05A145DD9D3E9E69CA69978EE47FCE294C68BD611B87AA7127565EF89A6713D583C295B3DF8D5F42C9D4E4A90EA3E7ECAEB7DC4292897ECF3A496D10B4AFBBBDD35AC46AD59CFF0ADAA99C2020A45257A87D4E4FD61061526EDD0D556CA195BB0D043D6099BC0600F5166F58849FAB80FF8E7D77B9E8E0DC8F958A07E309526C6A79F80C2779A9CE4D2EDF5A0AB0886510C5F68E01F2C736CFD8500EBD56187CDDF414C35C7E6A9AA9DB75D613AD579E18BCF416C13BA7B2526FDEDD4F0684412AC8DB5695D858162A4DED6F32102EE1E4A40D8E5BFAFF88A75623B5CC9510C4C53FAAC04850E605F4FD8DDBF81A04BC57D69613D16B83C374FC1A7EB3449A29584B6DF7A05F5F53F599B6BB05D0F96E671DC2DF943E6B031769706A92F73C2C737392341E15A3D5FF9994EFA4F20C0FB8120DA1F2659931E5264F3B961E4C5583E54EDFAF3C639F2ECF6704BD3E4D6D1EF5E822576636482C5E7C3ED6BEB67E1F1C7099DA9D14FC6431CCD1DBFFF96E1D1553D81F4C1E9F805D7D3FC67FFFFC9B1D8E3C8A6707F26590FEC1288356D053587C08DEEC0F29DCB4C7B3027D67027B5AD0A16A16371FCC0A0836FC10A386645F32EECD44E189B9F860C7C27466AF154C169D2393850BD6FB7368A6BA807A2790663255790006FBAB132476BE7351259DE171DB13C33A95F34DE9218733D2CA358CA0354E745BFDF07F1624878D5E2EC6958C430B093FBA511AF5D1599C421FCEC5624C3E6B025494CDD67328A3E7ACF189A894C81094ED0DD26131311B5E1A2DB1E7665F631AE4E9AA87540D0D03CFEC23C18FC0E8CAC8CE7C57065D1BF1DC9D1DE9B07A83227481BE92C48F37CEEF579C7F48C0536F634C64EFCB6F3801D8DC75F1C203CE49C9FE47EDF0A0ED6F5A322AF9F84CF7F89AA510B621F54C05FC30F53163D15DBB14767E4D14FBA76E28DE123041E9F5A23F9F2E705C59971F62E9B2D4D0E4C8B4552B8BEA499C0A6607D21D93CCDD540F16849039C78E5A44C55DBBA1AEAABAD700918BEB5E134C3445AB953D2585E7F5811640CB4C64FE0EB310AE6D53652D5C6F9081356E69AE9B29BA30C5E7BE59B776EE45F2D8B3FE517178F266DF5DCE2BCD4C097EE1F1B72F8808664768948E7D8426B9CE17F1B1D22A3AF0E4FA1DD768A5E641B51D0CBACFD3030585461A6ED99A3451A6E2C415CDC760E7E1A3AEE6BF0A2DEED6DE7223EE46D6B45A38B22DD5FCFD4D474322292522E3BEAF7F48F31339A4C81A21441AF438A20628FA55BC0802BD70B1700C541E357FD2F6F00961921CB0E8E9CFDC80C3E9F9EF6227D1F7A02414530BFCFEE950F2D350C7C59A0A18E77FE8A6F76A528CEBCC04A409F73263BAF858AF8A7E1A4FE63E0F14F6B22C15D4D0370C93693D12ACE67645EB17C3EE378C3F1E00B3EAA675DE35B9EC0B04352A1A672FBE52C00258464AC42275384BF9F1057876435A9276E3F736E3AEC7C19146F71BB87565F519A63EBA44FEE1CEC9673ADE2E7E5E394816798ACD2724BDFBDE93BA190B7C6D308205659C0EF3F0163AD42596C39BBF8C92BACCDA7EAD63DE7C68A072A61FCA33AB0EB533ED4A182C4B09A7EDD439C4C54F0F9D59D24C42E767A5053D7E12148ECD0126E5F77731B4BE6F50DF189A74A4927CA038C8B3C269E61DF7E5A3AA93C0A572AAA218988F5FF8DAF042F7A58CA36A069292812F8810374B768EDCCFCBA337AF9C362353942243146BED5F62921A252C378B5F9B41B6934EDBF6ECD7123273818276870CCE45B81AEE4C971FEC69A75249626821F05651786E952F3C533F525FC8B7B5F6EAB33C320532774256C42C01B7205AD6265ECB324F364F6785D5D54BCDC595B252BA48F4933C74F262A680E908D6217F81FD778E240D7ADD32B568DD00B1A2FCB2CB70D39CD39632D4883546D2FBAD0748DAE6D34E090694DFFFABF7705237947C6C8BBFE827F97009544A56B3FC94F67A972F2C5EFB19748EFF9B8ADB783E114470A7ADBBD1595E490846DF2E9531BEE796F63A1166850BB6B1FC2C14F429282EE3AD5B213835E8445F50C8711E06296371F912EAA6CBB756BEFEBE373FA80A7CE147BCF84A12E39FF68EFF2A0D40F44A304D170F20D49EE207611E67EC1AF24C244C49F842039DEB0728CBBDBD50BABB546CCE699E32FF668603387FCA38ACE7C89BB8BA142F52706D6898888D6BF4F99999E0003FBB62CAB4536B066517C8F7D022DAF488FF6617304B96D9CF3D45C1845E2443C59C3A6F6B3ECB173E9F193421AD77C449D96218488343F9E7AFBAEE2139078547CEBB62ED5FB12024C936E9D1F56C1852B392346F3B92219D09CA83E89F7EBD613518B29DCE4BDE26686F75617CD7E7690BFF58D520B16D87F5522F4F8BCBFF808BBFCDEB16CA08FBC9E71104E2923602D027C1C3B8AB9EF1F550C138970A9C21AB3A416A23BEFDF57D8091BAE54A636CEDD05596901D581308048CF77C5A0270D8AEEFCEC454CC1879C0B59C03AA4A2526DB6D3C2BF48482C8B47969C49E480F11ECD54C6A121CBBF1EEEDA08DF309F919EB484C1718CC373C8F29E9A8DFBDC3A2B64E3E3C91B5E5819F7EC4F8EA190F40374B43CF65D8EF9ECD2430C171123CE4FC643D3B1296A8B35DC8F29C4939BDA0D6BA4E5681A0304FC0B131D9CDD4E92FC48AFA4C6771F34D762CF151616F7E44C00EB5A553144FF3D2214DBFECF85205B8E060C4B7A9039A5A99C62A2C95B5FCCE42888BA45EC5FB1A8E6D7C49786445552370E45116E7DCA0129838B9909648A6F20EA06315FE643D660DAAAEC5167B5C204AECE536812E798F69E14F634CA9EFD711AC7B1A887DB433AAF6AB84BC2650ECBC864894B6B01413530163B9A0E0C93E7D67CBDCA1AAC561A35E4605BC37B452D469E01BBAF52CCC9E08329875F1558D7293BD73D7992CF3FAE2BA844CB4C3EA1BE890375ADC66CB1406E6AC043C02FB6CCF652186E8D6A59ACF284C0A4ACCF7DC8F7E57A94E3321B3839E2CF469EA737EE6E8980549B76829B29234BD5F877ECB298B21DEE6B37D5E46CD3403AC854FAE1BB3E08DC7096491F6CC7BB937FF4BE3FEDF68CD6538745BE7E760FBA1B9068DEC40641C3F3897DD9F90EDEB78530F6474539A9F61A0EF0528BE905CA2A512AB8B3E98CA0F0F7860E14B9412266F74B88CFE88A99935B38C4CC18BFA4791CD3B5575005419528735F9DACEFB259D167C250F782FFED6992436F35D8BAC8DA7BBBD8BB525F614A9CBC54CE98DB4367290035D15E957CEDCE9C84F52468ADD40F1788B45792FBEA514FACE15787C6DF86867A7E9BE693DB1C8876CEFAB16E60AFB71D66693D046C0C7ACBF96FE51188E61F63289DB8D1FF68FAD8E979F44E9C4907ED2DD3B9A11C23B1E7D472DCA16C3117E29333C1559AAB77B6F8A2928F9CA8D8677969D41668D7FD849ECC63EA7887A097B957AB6A2EFED953F2401402C249863E1A48015763F426CD818764B87A5EFBC29FABC1420FD78852A1BD727A634E2D7631B4E8D13F653E9AE9CAF331C27179E42233EDE93BE998524A329BEBB41E16943FA0B425B3184E6D756DAD71E4F185A601C5AA07468CFEE447841524999292C6A366EAF6525E276DE21F2FAC66F938015652AAD176419143762FC7C8C67FD0ABC69443C5AB5728183205B8C4C2197A9FD0EBCE2BCABCCB92ECE940534C4308E93F3850072784A16D4AD96220C1E824EF9053CCDD76CA78D834F1085D91AF9195A029E1E7445A67709E9EE2919DE4BCAC1C0FD2126A432D54E8736BA51CE906D9B784F43275F88E61B5E05A488BD7B2613E38D832166F4D236136FF3B88D0795B9937667F523BE902BC11604EC956A00CA3EDE437DA9BF9EF64C8F2288E92CA2B72587FA01E0E09D4AA606C8BB2F92CB9AE4C8753E910716EE44FC3DB7FF5884866463B0D0264019B39C4065CAD037911A44C0EF78402497E3CF7318F12FA666F555511A68AEE13448C3D7AB7E45480DDC241E0CBDCE2299E497382DFC84B7E455965CE6F9C9B64AFDF6C89E31482A0844C525AFF963280D1E00B4DB9079EC897E3DA2AD187FB81006F09E35E2A00B7B5DDBF6383A0F722233D1C8EC17B17537CA446CB9592AEE7CDB6D76C9B56A73E0B90C521BC745221FD083800899BD6CDF33ABBCA54235C522A744EF6F3A990853958B0C8C25320E3246BC43691379018A93CA28F69FE315C20E583C58BF0748C4546D89760E314F2E9A3222D641D6B4B963E4949F5BF278A1551BE72B2565AAE225D877EE56409CD9DA5D0CAC8E191822623B4E4AEAF5FC3E3700096E0C5942C3B97E65D138AC6DBCE6D8788535100CC0089017545E5697924F3A0EC31FFBFEB19679F1431DD0A607F173DD466F98266B1248D425B7E32F0360047D58BF1340BECB1126C3818DA4360175C6D7D8CAD5FCC1BCC9DC36C4E74C4FDFB70C62A98CE75466278AB3AEA9E6B79D16C4C75432317B375417DEFFD5846584F0AE5B05ECDCE6C99B3A03C216BF45866B1EC12EA0A5F7587C6DE1C497A2E08B85F385B71569EDBD406D93B3B1D0DB2E7F557561CD36E737C2B43E298AA9739004BCF55D7C96CB1438F2BD7BEABAA9C64B58932AEA808BBDF30FA4D363DC501741CF8A43AA1039E7B46576B8B299AE300E7BB49B61FFD29D0F814857931FC2AF69E6988F12C6731D619C20DF1D7096DF5C656C765A35DBF099EC5AD97B54F5A40128182B0508A6C5E6C7BBE9958F290D76637909E4759AD778E6924255A7AE038296127F00124B51F5F3742475E68C2BA4B2BBCBF39CE7463999A2C95569FADBAE6F80CC073BEA063CC5587D9DB5962C9B392E6FCD143F95EFAB1F619EFF7FC7CF64FE3D195916CB53AE2A956880EBD9EB199B87E5FC55D497FE261DE93C129564E90DF1BB21DCF41A53EDFD430213C35BF4D85E860C04C06A7CFE8126CE520C1286A24D6872949B702F09581F8351658CB86D44C1629B4A0F24BE07CCD39DD21D9AB8DD1B082462AB67338D0326B481CB4BD541D25DCAC6FF5A98AAD4B5C6B50BA0F7177C4D40964B46E96EF198309222D50005E1ECD5458C71473466EB2693B3EA732309B19A3EA582099C4A16BADE4850097BCD495BFE8DF97E1FFBBA29638CBFD8D841549FB213EFB29AEA862D6AE28FD152820CD0AB67965C2D08956016C4DD6D24F94BFCC9A396BAFD26AC085709F5899C4A6831E8DF072A96A05DD8B854963BD0F2346F13E5FDB7050BEB13B4DDDE99B2A60BB4550129FE53E049CA595351E29A07CE7828F387468AD313E11040C3C0C2F42DA056A311FEB561DBBB9910F080485FE31B1A3C46D44000BC619C578B387C7521E4CB99000AADAC71BC2FE7CE8EBEBD9EEDB294702EB294E0E2CF639B6766C12416A3EEFA84CE71C8D630851B5FC5C57F80F92A119F07CF623DD1CD80988C7CE6B6E1688EFC9226050ED3494822309E2049C0ADCA80327165283C8C8028769134D9FF9DDC6769E4EA422C5777215EF7006B8EB54FE5298D5386EC0AAC906B03C5E1180C5E2AC69E1C93053DCC41921E66F976CA35EF242F2A26C86F311F7B998BBDE5B83C547B4039F44B0DF8ACFD24F2928D4CFBE7DB647ADB1FBC95A7E2DB518A5A370D9A74619283F4F77F159BF5CAA1A489631458F8030C645731078489A144DF87BF847318F420FF5A611B4AB8D7E3E4DC2DD74F3C91E7ADCA32E4D4EF99C0410664A32B50A85D2AF82006C859378791E86DAF7C94ABC34C166B9AD0EC287AEB8F8E275E45C51FCB7EC3EC5B65668A07F8D8DCDF19F201DF1FD0134B3309212EA13CDF3E0A25CE16201B0A69C2830B7AE5F7F8A95EB38399BDE01FFA81D642147D2686E56C213C42F3866445F567252A47A95AB5AC483F2BC6C9E269A9F75349768567159BAF00B0A5809ADA84870721AFAABC61BB2263167D70B916871208C64794C929BDADA24D89393E40747EB6F5E4316FF5360AC876F726224F5319332B90CFDDC2AF9F401EEC6BCC002940EBDA36AD04905B209DCAC11C943DC58565D53E260494E7A3266533A0B6B98D21D1AAB449F08981B01B2EFBAB0ED4E7126E54ADF47F5C8C45D3DCF2C65ADBC8989FC7D5263040CA402B76897811D409D1B04A0C80A078ABC9C4567AAC088F1C20210FEA960236D485728326EE3515B05BECDDE2D1FDB5549260FC023A5BA9BB775C14001F88A4C5B35BDCEF9858BB8660D1ECDB0A780FA439E5E38E96BF36C1E97BD80CC0B11A704EB2A73A2E6C0495FE780397C157CC378F2BFFB86014DFA7774B0558CA6AACDAA5D29CBCEDA55CB321D2B635EC312BDAFB30C45C006D8248D6F8F80D037EFCF94692F5DCA16E244D665DDABEB2C39F0011BD0F624E30D2F6853DD46DA56AF67E51D0B34C9D075F5BC3A90393FC8F5E96CD8E029A7901191E66CF79D64651CD6B95870F483A39AD9CFB53B5D1C66FF7CDDDE4FA42405350DAD3B010F645C5175461EBAC27731FA4466461AD472A0F37B531227A59AAB687D14409213EEA815AE4F925BFAF43276947B28F965F99AB15948FE8074F7130F929E481041DC9372B7B249E1B8A5AE6FEAE678C1DDE54520167ACD1CE98EB678DDA102E7F3B8B432228EDD58AE4AA4CF9A24ACABE95DB1F544421B177E30F32443B8A13732A45DDEC167958C4E7A35E65391C59617EFA7E4A18E0BFFF6ED42A494B070D7298944A7A2654663BF1BE91C73F023B6439BBD4368A191E1B31B4229329E2E81949E46F2AF68609AD9A72DE6F75FA4725A4B50012BAF07F460B04FEB6B15483EEE459135B6EBE5EBA62493F23B4B046F1D712FBEC4E9DBA3FA5D0CA2DBCFCDBAC154771B39385F0F0834779D6B35F888FC881A47BCF4C074FF9195B18685B30DDDE3899D3E0D553FBD6EF5F8788BA2E977C93551E92288E2149804A3B64743ED730D265E82534D123563B3D3311264DDFF1B884043EAF01B8333944BAF360F09FEC910F44AB8AB1406FCF4286F04C88250CE337995994A9BE19B0599307A0557F722233BA3D325EF2F2C4504ECB5C157CE80267942DDB304A4CEDAC15915639B162776C4C853B98E9B45CB12D03144696C1DC81F3EC75DCB2C0D7947683F1E39DF344E30DEEB7F21552AA6720D81ECC47EAB91988223172920012B0DA85B78F80027C3FDA06F30E60CDF1F9DAE40188F51DB83767E6550FC97C66BF183E580F55A9FA682504A86C4DC29E81C6AC3973D24731C630180AD764938B71DEEE937DB1682BBE9A4C7D49349B615B688E13F3DB8AE25439F4686217BB31974584320FAFE77E167386C6D54CD8F9F0082DDF6CEEA87673F2D1B5F15A12A8A844D2BD55E7DB2D842E456B321432C62623C4E304B20230E7837D67F4DDF293F46520BC957FD952234605126FFDDEEF94FC5E77D2013D1A49CD7020DDEBE9E8A704BADDF4EC56E2A47AC02E134BA6B1CDA80FA5A0E2530E4866C99B163C3E378CC770FA5E3855CE5C146583D3783474F83187F9AE5936FCD448BC8CB91E2E29965368A9F3B140D9DFA6E95CCAFE976A93C6730B6149CA739816C02F4EEC2600216CEECC8C14073CB6C93A6472119345BB0E0BD96E557FF284DCC19F90784E39AD0EA77708A37DCA7AD9403FDC6655A45028D0393804F7A4249D3F592998DB96950F78EA46592CC53A0C197E141F930F58DF7BE81FED9B158E3F232D4DA926135CE6CD8D16FD88C43623D13F2401587323B822F581E3D2935AF2ACF3E4FDBB682CD9E0D2C09C995B47AC930AAC36CFF6C556B94E1DA0561151EC280806EAB7F9ADD950B791B720DA3442BCD0BA3528FAD1E11997E3FE8DDD4496981630F6D664960E5DA44984A2025499BAAC975C12F57CA9F5F425242BE527C64CC062463D03F93530157A256DA1575AB97EA7528986AEA76D5B1EFC34A81B809F5979700EDC9D3BE902C28EBE1741E1F1109F5FB1BED02631FB0F25867119BC63D6443A22013C6B38B1E2231DF0C5E1CCC1B05E34917285B003AA9E13011458B2F776A64A63DB3BFE85675F8DA25C8E697D594A6F801E78EB375472F039DF556B74FA908260F1CFEEE87B938CF6ABC4717039DF7F29E31F503CD38BC3FA6441A0CABD24C7EB9685C1971EA977CC7166F3C6FC857227F04C47B239BC9B66AB1C900CBA95637A72948037601877D8AD8CA60D935C2D4A7175ECE306AFC15344DCBC568EF74EEAF981B4C28AB56B2DE51A8CC9D3F2D61B06189CDEFBE371C3442DCAFFE1C0555ECE43523C5D00DEE2C4AF477C773A4CC02CA63337489DDCCD645E6D10268083B3A148E09D4902A3E15813EA8F6E27FFC022B144454667BAA3A67D01FA2DC0F26FF7721EC6F0FC13AEFFB2F7111D181B57978277BDB694F6F34076895F91A078F2BEC3303AEF47F5EEB898164CD4D80426B9593B0F5473C6B2BB947FDDB377A957521F9417DBE4162A1A3842218EC2F87FA87D4702E2A4766B1A8D14CCE492DB85F7D028D0E77CD29CA26221E1A738376378E1D7D611CA9064D021FF37C4F90EF5CF75762DF8C79474D1CC66D1B655BD5E30AD8C0B7676E8E96F8BC155CC94584E4D5FCB1BDFCED948F8ED31F5241E591DB6C53EEDBAD4B167299DEFD4A9E7C64EAC9AE5B1BD4C0C8BC29915E7B2E0F32407040F390DB5DE243106CD74B6FD02554FCD534D9FA3DBB34133FE1AB83162B5BFA019B27874CA226AB586DB37DB60EEE066E2E71A74321D15254C8611EA500329A050526E6EB229745EBEC1B2E01AAEFA4D4A839DD60C5B5B4F41AFAAFB3DA78DFE5A3108B837BD3F5EE623D878D39AE6C77F4CCFAB9D0F572018F46BE01E0CCA9DFBDD6C6438F7240CFCF1C0C25231D72A2C48EB9B553275B299DE98D02B15075398A837EDE94A85BB834089F969C06B45D5A33C548ACA31CB006D5C7B4B2CEE7E346F96415161D79721A22A29D8B2EDC3ED99B08557F5A170BC0EBB6526821BC5DB7297AF25DE46570F975BA8B5845CC7FFA5A74C201B51A90A2293B45D77D52F90A2B9AB42922379DA819B6B54138590D7E8ECD8B50F455C492B85ABDC1042E1ACFFACA92B0CCC629C1135DF4DB0FC76330043CCDB2E26DE48574113DADFCE23B146BBE8F5C22D501778AA5DCBFEE4FCD0173BB60EB93A0796E6E2E4A13676B39A5BB5E4D7A3E292DD20917CA3CC170E12C5C933036A31D80080BAE9B45574D70B5282859401145DE90810D0DB0D6E76A222D36E483C3CD9DFA0923696B024F0D06ED83DE0E56F662C3F0884CEACAC9A95F6632889420E3E57224F43C6EB5888DCC95F14041B66096219D49B3186C94477432D47F605788B34F491FBA6657772651BA8F9DB8F469898315AF514FCFBCE15FB82AC5B2C043346B152E1632635C708D61A000E420813C3A31BC1DC1352EFD258244DC3454157882E19486D6A74DA1021A0431A2FC43D03B7FD8993422D30027183E8B2BC727DDE4AECE3DAF454D6FB7BFAA4BAEC281100C2A59D2530AE67421DF76D8E9D01EA507544BC8799DF19FD2FBCB214D3B80435B5BC9040051048B02390EBF080DE1111178A915BEB018E3EE5A43B483A1526532CCFB48221A7319EAF8AE2215C37D7A7684FB7B924D9079EDACA56BE7B57AAFA979BC20992A69745E91A3B74911D238033AD90B8C6241F1D72C1CB8B2672841F61119B5A6298D5B34213384DDD540B965A600C682540A3BD8F466AA25E75CA34883E68EEEFEDE502BA7665AEDC133707A380627F92A990E2E27874CA226AB586DB37DB60EEE066E2E71A74321D15254C8611EA500329A050526E6EB229745EBEC1B2E01AAEFA4D4A83E93CC0DD26A8D770423D127B762AF62E3F3BCA02510AA7C67E14B038F075C5C0511878324C47DE85C28C9CCF10245325D5D52717ED331EDEDBBDFCD4DC61A9E63100328197EAAA40DD8600B21E1C8AD0AD1187B69E29FA78FA7D618F7F82B6100C102EAC3DA727F9E322543355DD0128F5420E17564BA6638CD784D9E9A83814EC0F46C6F2113F35D12117F0C93948F7C789DDC46EF7842E95FC78CB961DC312ECB58ACB1EF0D614F2E9F144D464124EFA9E06148E78DEC111050B93F0BEAE9BB408392FB4506F350DB7ECBB6E026DFC61336D130B1EBFC9A702DCD80915BE21C7372A3B767068E27040CC5679158C166C159794AC3FF300F5F9E53C1F84420B692A14500F98C72FE2E52D6643D185D217CFAB3CD9EF9C41BB297F65E164B654EEC777CA127A0173197179A3003783C2D48F7C2E1B257FF189D40030776CCA162E0F130F1798BFE2B588CEFC16BE109B03BF936AC25A28785951E49DB104AB23BBC121C5D5D6BF441E2A49B61E8E201346EFDC76ADAA55CCEFE4BAA25C540483096F36D767112B16009FBF74708EC3E38982DFE9B31B75C09B524B91E64461865BDCAAD85445D208E013874415964E448929D746C1AE26A6B932F919577F60E836BCD7937E677BA4D8134820696116850AB81F22EE3691BB4F306FB9F4029BD87D3C9BDDE5ADC1B3578F099809A8FC88BF3866E9428B6DC2FA66C8FF172A1283B3FA3D76561CFAC53DF900A895DA44CCC9A34D99C46C056243CF601CC3D2E300B32F60C74E739932A2F2CA0B2C91B9C539B885A4A33A164D35B6949BD5EE060BA28D02DF6672F5FAFD386C1CC5FC2E3AA8D1B1E91B8CAA7A2CD45EF78E822DE10B4AB0C4941DC4C01A4FC21BCA8BD1843ED0914E511350ADB4DF15E4561083C59754749A5726637CB64200BB665511F59B37408568F438D8FD0F8BD34BE5B2B789F5C733E1DE46DE6808F06311152E94DE6173B784FF6E0B367FE993993E0C3065925CB492F60F023B00423670119FAE218E54A5CC0C73E08BD6E8883F0FBB83F243868D609889B0220D7997728ADACF39B885A4A33A164D35B6949BD5EE060BA28D02DF6672F5FAFD386C1CC5FC2E3AAC3B31671BE86983D2FC361C7576CB730B4AB0C4941DC4C01A4FC21BCA8BD184BDC143E13AB03AFB8B460FC86564C5797068DDBE9995E5B798B9993DA100D94B05C0EF0620AB84CCD3DF8FE4261FF4BE168B4B471FA03FBFDB59820DFE0BB59532D82B88E154C051AAE9F7C2DE99D8F348F3682415A25EF8BB1DC0A5EF37015CAA780A19678FB2BD1299374BC9F7CD3F416A96D70D0C8BC84ADE068F1CC158D89A2073F04DE9A442482670A75534D8E2075929024AAED011EF7B27582DD1DB65B3EC7CC7FDED33037E8F56BBCE353156F268D9BE25F3982EBB4731646FF7E5A2854A243B2381D6AC9A8B3C40476B25CBCC362DCEE464C4099972FD4A763F48D21A6FE20FD68323E6DEF620F656CF28E41D8E14E6438405C2B22728B7AB9C088E413074EEDE5E07B4994CD2049A185678BDCC0F26D406900C223E3D3D73966F8935A072AFB238380778EC85DED3E9736DFCCF9C7A402173B8C5C616CD8E7A8EC8B9CC837A6C7CBBA15E62E6C50A7A828FA1C0FC246AA805F1E4CA1B6FC1138CB35211FAD88DA43E92D5C73201AEDD60B54F5E5F3875B6B412B6ADC0980821E0A16DDF2202C2097C974C14BC490612D356682CE5CF702E6CFB49F4F8B6318322DECDC96FEDD79B47848B2B517A6A0A519C46A0F17A81817F1E50D36B94BE0A2D1D018602B2645F3C0EF839BF2874DFB5AB6BD5F8B4EBC52574E2E6FBBFE698523E6F84E8906CEB38CFE07B333AAB0DAC201878E9171BB0EB56A753E4FDF8A4504241484214D578263A3DD058187C4EA24ABDFA707BD1004A9125CEBBC8913F250CCF4EF4AA2D4B89D689CC8E99EA12BE553B8DD975350EAE6D121543C13BD03DBDD57ECA97C16E6461C710CF4052B84C5821BEA529C55227F401E4BA9EB584C7EB26648AF893F20E3C73E038B2C76DF8C2CF35EF70F55C25CE8F59EEED9164FDD8C3D453708E101E456346123448D658C94C0869AB7674CE7EFBD6258F1FEC412F8941F11017D143F2C4CF0FCBC32A3002C132208834E3C52CBACA4AED32A44BD9B515936CC0394704099B511F18BF3F35BCDBDD1A76E49A45DD5DA51ADB54014B3284BA7A5BB9A69A60E35966EE12ACFC9780CE7EA0CDF220AA0E3E7FE30E110704BD91D2E08EEF33345845CC663BDEDE1801D04A682119B6E7184BC8EEDEEDCE8F8D62191BFFDBF4852356845FDBABAA4FF5809935FC86BD21360285E6F6081AEC6B26EAD2CF3B038A05381C7153DF51C7A36B3EC1A54AA44F52B80B855C90167BFA28C21458F4715EEEDDC9E43C5D9C23668C7615484FA19E9643032D91CD34E8BDFC213DF36EEAF780C444E50E01E9AC6804CFAA9F2E0BB0115F7A16179A22D5853C765B6A6DD59F2393DF15051182E6EF1700227477F25B4F74B99D0064AD68682D5120D932D827A4C795468440BBED354761639E22B305346FD74EA67E9D1D4A6F4B9F08ED159A62A976180874940B891E189B2412190C85BD0162382767EED017D37D9AC7579F3136D3D38DDEA358FC0D7DA7E2D81AC17EF82DE6BBE5065B37FA86470F50933A143841658747F47779B3C321E4CE4C22363C3F31F2196B65DF63C56FD9E2EC886CADBEAEF65CC4A8A00EC3792AC5FF6691EA37CF4B7C7EC938D0DE37CE22A11E186C13299EA4803085FF7BA0771D7B376E5866FD45B64B9045936BA753A71E2B10FC7B21579FF84069562AC291E11D35235DD45C87A6F7A016F9C24F7D60C01B7E101155C90F0D3808EB0528FAA080769E5E3F6E29F7C4F97626E202A32C47C7F62732DA5BF0B6F492F4BC56550DA1A9371BBBD7D0B1F026BF41573CD2EAFF09884CD0071BCCD5156F849FE6F8545CF58F14B969520ECDA988E531A36CDB5031EAE88850E1F33C4FE43E61B0482AA0B198EF0194EF383ED964DF673FBFB6096C0844087D47B39D57ECE891059A623AAF24921B9BCCD56818F43462E88FC0415F58370CFD8C9087C23304AB7F39B3E1CEF3E035B93AACACF681610BFEA6F41FBF1A8D0D18E91252E938120EA579EE755F8735AE26E651670D344A7178D0327166931E90B35D1ADC9F583D225B1001E50CE8B0791F2E02FC1A2B5631D9D27A79F2E0DB0073CD029F40A2D73AA495F3F844CF22B1C383D70C43D90530F59922BA8D297A3B38B92C21321AC900999094B6E49F5F8E67E8D3986A8D6B1F7F31507288508615F0EF11143344A74B798845BCAE5C3880E01014A37FD18A1025B51A3EADC8A927A52571A2845CB71AE284A8E4802BE0B8944104E858CFDEB9146634E9A40DAF7E7CF3D3D4319C9E6B0D2B0ED90BE7EA0AA987F8A58A9D5BB6B36FE0A6BA8F1388CFA586376672795FB3F22E743C55F099DE488217F97A9BCDF74289975DDA109F0A7530D03AEC15D57DA947AC0C653EE57887745655A5DDF88B9B40B8C576F244209047F6CEE2A0E714F8DC883232E283F9E217C6E66AC6AAA17285BEA81B15DCC5872435720C2E462F1127C3B41BC1405B80FFD4E3B5C652DC22DCB3BD0389D7E2D2168208CB590EB7302FFB1EFB037CEB53E5D1830507C1572D6110161E921E6FFC49DB4445DAD603FB4E83220DA450CB9EB4D988DB3AD6C9D36FD7D50BB8052DE49AC9B174098FF92189817CFA066D9A4098ED588C45148A54D67F8DB0B0C8A17FF049AA87EE8DFE2F4F8B7284CC0E041758F4419C11005EA764B84575FB61CE2E09EDF2BEFD4BBE5AF2ADFB2437B10717F396F899D3E30431779A2B492DD6B578B9B458A8B11AD1E8DFADD3834DA854FCF95F81C4FA2D8F96D3E4F860BECCB4C2C3D10A6FB7750D09D4DBC28FEF753B16F536C5FAA942D7C30B9BD0BE62AA96DF892EC27B8B16A54947441C30EE4E0180A19E8FA094D3DCCFE591ABA767AA6F7962DE2E2373DD45C3C88A7B2915 \ No newline at end of file +EFE643AF98362E004A0B709FC1212F80BAB72360DC142D08F4004331133594E20668B50E06A5B790C162021C4BA45A5150DDD680E8AA307A3BCAE59F8EDAE2F3F3BE296208BF7BABEAEAEDE0CE8792AFBE8A31AD3BAA5F06C5F6EF7962292BEBEF612077D2C54BF73609030AFA17C3DF7D78119051188791EBB4D79F2FD9488E81061B730D1F58F0054A00880AE7604B8CA1145F2690D2F3EAA18F933258BC3923C608BD8C16DFA2992BD82C9FDE1628C94D31A8B3D3F98CD71665FCB0AE232FEB47447B8ABE33344078508644FD8BDBCC177176D506F77609B94A8AA1558BF53F365DFA4234C7DEA81A08D9A578058B28D2ECED0A902CC0666D001BBA6E441A56A5A430E0F2EFFED4515B1EC7032739BAA30F57868E734BF72594331E00EAD996AA23D50A59A2D4151B4B041E9400D595CAEE695F68298535F14B29271C1C54CC26B74DFB34B311B3620001A4BB13D3773F250616F48D2548F5C24222BB073C50336CB9A87481CE86B59D3377E99B2306459D775DD67DA3EE957C3740B6F777D198B88A27CE86CB795FD742350A7F39F5FE058B3A382D1B8FE121EF236415EFFDEB739B19A6EF285262C1F0C845C9A7B1FADABA3E2E87AE3175A1C610FFDCAA148B32D726D741F7DBB8AC0D0E4D8BCDA02F6F44F1A2171999237D22EBD42977D2C4CF4E1C8069E7E7056B7A9157AEFBE82B4DE2655C6E75C8BB68A9AE2AA2F97E66B30B14F9C79C3D192AD03376859365CDD172CF03EECD2D99AA720EB15EA176F757009C01525768919D35D71FBC6F807BEC94482214C91FC07F2AA22F31C56C750A0F88DE5C67A312BEFF4DD509798ED7E030492A4EC1ADA3052442845FE367871E2D52B4595016F1F8375788BF6C3816DFC8567C5B8106CCB8DED71D8C2E216FD5B2E9E4C74CC1E835239156FE6A580B57B8C279556345B70A0683BD6CF046039D1D5F3D98383B3DDFDB43DC1394ADF3C401FA87D11AA011DB68CA3C6FEC41538D807C94D7CE8EDA12A5C734543227D9522683C155B54ACE43FB50B2A7559E4783F5F5F796504B389210B8A304B7F615522AC2A27EC188F5569B0FCB2AE006B22B1DD0FD8FA24FE43F24B62371F795752D026CC5CBE80133BA8DD60B2585ED3CC823C34568EFB7C669117FE8520B21FE4B66640EB747BAB87AD132DBCCAA1142E82CEAABD687A457E800D2DE96D43047C165B71DEF5FEC951EE192414667E370585895303C3606EBA44F97315F2EB2B3B95A0235060BE892C0B3FD7EAEFFB3A9E04568162B0ACC8BCBE37B201CD3CE326745FFB4668943C2CEF294512381BD15565F799477B0CF15BFE9A60630006705661079639C81CF5A0F6966831DC548D5EA19282A980BCE863F35720BA54104F7D0CBD8D7909C06DFBA397021DC816DC1AE686E5736B88F0B6D690B95E538F0383F8E85E2B1F8B47C569E751F5F597136AB1364DCD069722037D084688E54325B3D4D54C52A8B7286A581B72644F2B549974DDA1FB0439DE1C52427F6B1CA847BB206C761A2F3A3CE20847DB98F74F49FDA0FDBE907F54E60D5081148609905E4B2E09EF4350FA0362AF990D14AE43D2CBA05AC537DAE102498B52B4F0ED2FA0F43CCDA991BD4BA62BF1DBD7F460B39DC225BCEED3203BCCCD276F037C15B39408E07A7A37302F266DD3CBA633CD602828E512B0C5DA5ACC5209048FB92AEE387DF95C3C6F05242DB2227129AA235D569360A31E2BAC02865DEE3960400830C56AEBC052CE37C76D5824A84EAC91081D5ECE718839E4066AF477750308FF8E8B783A035AD99EE7F2EFF0753B9555DC13127AA3298373DCC394164176BB991E9FDDB7602058E53AF995C7CAD3D2EAA1B80FAF63FC240735B0CA6B9BC4ACB3C40A37A3F3CDAC9E3B1381ED9F21F7CE334D87887A0BDECD8F1F231830CB580E5DC301B03018D899514B93787BE604D7B9B5B41E13EF492A8886C50B01791154071DC11252127EED15348AF2AC9A3A1C66CBAFA68E709A2E0B1F32B2DAA5D21E7C4831ACDCBFCDC80F27A29F30731039C403F561B8D99992E3E0F03E6179291D1BF086753B46B24865EB05E5C5AFD7498F949E92413D905EF9564FB09373391AA0475E2BA9AD6CAD5F316CA79096C59660B2941DBCA200F25E344DCF2B71298766892EAB62E3E72B47778FA80334D4093C2E5BE133B9C13E1754C16C3C31EA17AC5B1E9D6A82A84CE7537F2C16870AD6DB7EF435BD027D255F04F074283E759DA2452CBE19E99BADF3A0D6D50E32F36A61B05DA1C8DA466B39282E142E69A00B871C872FAD02993D2468CBDD3AC7FF7283996C8AB9FE864DD783D3529AD101AA7FB8A4178CCE6861C980508A847FBE06051F7852187CA86C14373BBCA898A8E937453884F5DB723646B9152689B4E2E18164B5B793DC6F15B3FD4191B1462E9FC9B19759D534CB09F6F125C4E33CE208C94E43AE4FC4D051CE15EE294B52F5028266CE310FD83F504DA7C0ECE14FC8D1E3E4A89C6A7D638FB4D68354A92546B5C7C4636C0D635A520FAD72C8AA97BC1EBA2895FCBB3FCDEB62246A3D9F386928F97EBDE09A8743B4505492FF3E90ABAB32654959880E4A291F010B2DBF6EBA8E64952DB68AFED3774D563F9925D368F6EE0F94EE3337EF9EF80931AEE947C115718A1148111D2E69CFBB9518FF48368698C089AE59BFD38F9B69F4492B81FFAD9BCE4FAF291E08AFD68C828E30F692E613BEC38EF52F0B4E855DE72DC964B0D49D3414B3B67092AE1E8A862BE39B890F427833BA4ED53D42C1EFDC329CB3062BA3EDFD921240D304BA96D3348DF612CDD24AF7E4A85972CD448AD0A44B00814B9578DA2EC0BE52D0F8305EBA9DCF2486186F0F930E10A2976A942DE435344893548872117EF5215673695784460B9942C9549B6833E7630FD5B8ECDD8C15F0D8EB42664BFD471A00BD2C73B50119ABB0E2B3C9997BE136AD5495D18933B802F5D9CC21A9D02081A622524D1AF44AD3C36FF281F317A16683D35320769632AE5D7BE73D9CE8E2D4A04677D521C0C3BEB2DEBDDEF1500A91DA3BF261B8F3E450691E602BB4EE704E2B16BBABCCD35E06131E894A53C41DFBCC4A46434F616A109DC664FED07E6FE8FA0C6FF0B7A11DE2AB65446633DE634B9588073C905809BAD32DF3676866F23F6CB9DE77A83E8FCA52F012E56A7764FE69A0A84ACBE85F549FBBC4E8DA632FB7E70C2035C772ABD5296AB4C872DE25843CF93EF8A502938480C323822EEADDAEE431DF3711F7043AD4DDBB4704D243B6DFCD7EB629267AA43894A11085F07645EB03A32B61FE29527CD222994A21BE8F108F7672E220EC3BDE02F7FCB0A65591941489CD7727B981A9F46637789CC40EDB0608DAC79947DEAFBE2CE39EAC9A9C0D4895C0FF0E5E7588125C03B9CC6741950FFB209CC38024489C4CB41A52A2551F649D742D781270544ECF2171DF580F62E3085FA1C8AAF44EB05C4BED224BDC08E52B8E699CAE7B5902F7FCB0A65591941489CD7727B981A9D81E01481AA205D1287903F849507EFA77D0B39D1C198B0838604DE94148E3EE9CF098D5AAFF3A4270CF5E4251AE2C324D3C39E648D55ED4C77139E5BEDA260CD6BC67D1048DE879B19DCF56E4363973402A6B18DA8CC744AA90A843767BC4359BBC152EDF0D61406BC69F8B63B934FA08AF925BAD881B25B104B8CA928C154513A1E59DF691CB46F76FB75CB277F3CCBCD85F97B44B97DA63B68DED02A86F7602F7FCB0A65591941489CD7727B981A96252FA784009C02A59C68ECBBEE349E20A96B9E399049DB5F2A4DF8B498EAB0657AD7C2D2C0FFCDD5FF44B93C94DA8277AE570EF93EF0BAE0B417B89A2D5ED8DE9ADDAADF40D3E576D532B4D3267B5FE02F7FCB0A65591941489CD7727B981A98CC207739970C8F2937B4A07FC77ACA384F054A17C2A6BCD0418379393223D743C06564982B14D753CCB7829F134E6F09557B191B95F66E76426762CBEE31889FEF3E74932D04119DA3EC992D336C36502F7FCB0A65591941489CD7727B981A9FA91688618D3A422FACE6FC0018B3DF108AF925BAD881B25B104B8CA928C154520576A4A462BA3A96C90BCCB76ED3A560C8A762C9188D02E07C5E9AB26C6364D02F7FCB0A65591941489CD7727B981A942D4B8309F970B5496F9B3C2864F6E3CD68BC9C6EF87278FB4BE5016989260D002F7FCB0A65591941489CD7727B981A96DE46850C2E498000B9AF897171AB98902F7FCB0A65591941489CD7727B981A9B44EA8F4AC8C08E6A854AAE0FDD94D980D76D0C12A318268690089B241AECA731F51D75BAA8EDBD39EF13D490C2C257C18236FFED80489848B2BEEA20111F24102F7FCB0A65591941489CD7727B981A9FD99A4410D814A6DD9761FFD09D55DAF8129619707A564D8947C1293221E826E4D2BC8C70583C5E744C74D0FB141AAC502F7FCB0A65591941489CD7727B981A9F691A50DF3653EEB0987909E1E355858FD055BCB1D3D5DFBF739AF033715740CEDB7B27D96D6FAB084CC5A592F5D345A02F7FCB0A65591941489CD7727B981A94E237E414F7DDD7D613C1F731458453CC83250B907675C7C8257D07CC03DCAEF1B263AA437DBE5C2B9E8490CD26F3C2FD277625A9B58528E9D31E7921B0B443536574806CFEC4EA6090F072E480AC37381A75E6954C0171573D728FFD4370AA6D642848B685185EC81127A19F4664AF570E43631628876CDBA595103A7FAD5BCD6BC67D1048DE879B19DCF56E43639738A1B8C7453FC349723B7BA45A241F5BADC4C71D1CDF742B73545888CDDE215B4B89F905EEB0BBDA8B4C4FF093A132C42AB967CD8487A1F3DCEB8F530E3BC735894A11085F07645EB03A32B61FE29527C90ABA4377743B0F3FA6B76B5BCCAA102899D4B0352F1DEE1FACB4DC4F2E62952E863996D5DF73058CF3CF153708A99271DB3B6B3467639B93051FA0A53D82355F256C402AFFEF6C3A2C807CE4A3975D5FCB522AA2323C3CEFF53D8F63E92F7169D4932D319D430247BE60465D34A53F27C276B9A5F12A4121791F16F3369FAA0DB08DD4F8E8C27A3EA3CF7C6310EED53DEDB4D0ED7E211FBBBC602054E2701211BD15565F799477B0CF15BFE9A6063006B2C2CF5AB7F8B43666153A8A16EFB52B045BFBD7EAB60B9AB0C518140E3C2AAA6F35613C6D8F3BAFBBABAE48EFBDE5D299BFAE30FD8DB6EA8DBE1117195673A057B467F1A80F6C2CC818324FECFE8ED21B93FF1DC1DBC1D457779EA74E50EF14E6D1A3F30DB024233330AB14B2D061FB02C848E91CC16E5D887850ABCF458B2EA4325ADF366592A8A67410392F4F256D179FFA511FD1C44FAD45611C5D573EC2C4E1AD9D9EF734A89D91D64860DAE91D5161E440E4E7212B69C621E6B24F46499B5511134793476AA5DF9C1E5D5461422692AD80A82565452591AFED50F5513CC67B0C106F8D24F45C46F751F5C9E878A158E4B50B86495F2CF37D80C726BC46D10A41408E34868F8CE0F7852C6A50C5F79DD02A76EE733D478FE4C91B5B0EB1876E1E58C4F8597C421D5BE7FC859D13B849398FDF4A45F9DA57DFB698ABDED94A11085F07645EB03A32B61FE29527C9E4AE53B099CB8513CB5F2E9D5DF254E02F7FCB0A65591941489CD7727B981A9D5DB5F056BF83545055AC63A2E9564661876E1E58C4F8597C421D5BE7FC859D186117924E6E576EB0C4704CBA69E6E3952AC346432C704FE09A657BFBC896CCC02F7FCB0A65591941489CD7727B981A9BC677745E2182B983D9D63DB0C08DB778BEB4DBAA246D99247108D9FA08154ACC14502F8A717D3A09F24E3AB1383CE0E50D6C3C414568EE3FC2902DD64E654FB0A49F2C7A668C8445947C917A3271D633BA68F7C47EFD7A2C079A992264A504D249657EB6706E569D6702616961217CB669E97C474E6BAE9AA66A6114EDDF7CC4CB0ACB61CF24AB11AF0940E03D210A32226CEFE79AA6831C46A0C5B8710326B23D9711328161C20B46E9299A8919063A82D5ACE11E3B94FCEBD37480870FFEE9FE0482B2506DA714EC6D18AED572D3D85A1262D45D1A93B7464C5EB833B6FEBC8653C56085C3B9F12FEE612188FB8BA6559A5749F531BD56EEB34F9F12E6440EF844E42553AE6590C6ACCD31D2F840F56565D69B2FB38A43D3505DDED4B49477F53024252E555EA80ECB52381AB5869D6400DACB4778E1D9F306F542601AE68285EA143512C9F84D51DA724D7D4E44D30BAD85D73FDB52E0225A942788E393A77955519B971B0E52939A55A6EAAA7440BC24EDDEF1A50ED8DEC7BFA5C7C0F63C336423B75D125BE0363318B8B433B3624EA3AA0D06A78282C7F4199D45A41147A778CB2B4DB69A9D7052CEE172F17435BA495D8DA92E29776B11C80D8437B993C0558CECAC7FA1ECC861913757886D20420C3E9EA2EC7B5DC78FD036C61BC9860EB91492799F32AFC13CE89F4701C444B0D53E212F2B9A3AB1789DF8120CC1B00BC89BBE3FB70554914D1029A51A47B896936AC1DA446756B502F2E6B9CF2175F1531600B24D5106E8A29A3EBB7D4D4737EC1FF30AEBCB175570B5F59F8AE55A74259944F350A37E4B6153B4A1C24C7EBB93EE80FD5E24A08801F5BD981A5AB34B3CC236F59EFA5A8F5263A440E52F919D7851B0C81C09DF6232D731774FAA90D0A9379536DE15C568A8D5D304C529CA886FD8841BBCA1DDBA389F1DA9D7B098A5DD1DD34E4B65D550BEEDD00CA0B7D9D55F41768F0130E8A3BC03914F9BCF5FA6BCFAC72EFD777F56F812D30FBC8B576DF5729B028426EF39A7D9595393AB794F3080ABBE44F197BC6AC77542D4FF7E82C5F3DD0936266DA4BFD82E3A0F54B9FDC2C3E73D1DF7796527DACA91366B2299427CE395BD8FE561B3C37C321318C2C4E1AD9D9EF734A89D91D64860DAE91D5161E440E4E7212B69C621E6B24F464A8873832843774C6DDBB76147C010E017E5EE54257287CAB02086170A792205A277E2A18F9C2A7E37A3FE01722BAF063981B1986594FC52D86E778B53FF61206E04487125BE2900FED0C8044B40B69E7AFA1D07194123D66D2225C2B975DE8BF09A6849FAAE7DDE1440139C20801C391F49906ED4504FBDC80CC3BA5FFDB4CFAED5C688BD7697ECD7417FB5791F3608DBFB465606A80AF120377ECB30E8B5CFF31B8CA83B73FCA2716590305314429A79B53AAED5DA007C7B3D96BF99A296CFA05B5B4661DF78B131A4BBA57B64ADDF2BF30FB867541D12B91BE54A25AA27E47FD6EFF63849D92EDEDAB03FD065D25CFA7F97BA269F0909F02DB614BD573414602F7FCB0A65591941489CD7727B981A996FC994205128AD0071D6661C193FB2C438ACB246D7CB7F7DB7484AF0526696A99FBA6AE8C33252E187AB9F7A84ED0AD2428290CA197A9DB90D8256D7A5BC26704A1EA860D075A6D4C35E5F9B05A85F3A6E76812EA801DA832AA17CFEFC0FF6E65C063A09B87553D0A35E4E48D6FC2CF26D916425BD03DE883AE3D2F865D96D294A11085F07645EB03A32B61FE29527CF3C710DD7CD27247A79485B3692E9EABB3998040859B9933D0D4FB173ED7AF272B6B72894FA2A2D9CF38C70E3FFCC129AD1DD14429DCBED050431F990B6688554535F6E7BAFFD64C909C3ED96AED4AAEC3047E2BE71372CF3EF076AD49F5336C438ACB246D7CB7F7DB7484AF0526696ABB37F81AA73A9F1ED2BBF0AD03040EC880AAC9E1C43379236F4B8718EE45149E591F4B965BB384215160546E6E329140CFE0F1C2157DB5372A8E44D501C6D1BE3820973D39B9E9DC47B1AC43440B8F510C614075670898E8A29713A939B6F62937B939A13321B466D47CA080BD7452CB10355739F83EE47FF1D15516C6F0787DBC3BE0D039713A4C4ECF9EA1E29CD611B014E0C208460FAAC757ECC1C16728A99C7E1618BD00EDF037C4938ED14E2057B15F0392A16DF0F9DD894F945453C0EC06E3096811662E5309858D89F3B49FCEC8016021E2DD868BEA5DC0EC892F81041221E8AF1EF3C27D7192D096F7E323235F1B62DC2D3ACC91BCCD742B6527400153E5E0FA106F47CC113F796BA0A499090DDFA5A3401C1551D21A3F24986EFEBF08AF925BAD881B25B104B8CA928C15457DC1EB66C2EB0332B720AA4B5C3FF1DFBF2CD353DC8307DCFB935C1B6C34048604491390F6568FC034D99D4127F14426A20EC2137E4BD7F317F90334A6F50A61DBA243109A2368AF7229B724D84A00C377955519B971B0E52939A55A6EAAA744C1DF3E31FFD083D6BD59DE4919E99B4BC1E42D9FB041D119BD39FA38369BDB6B9EFCE417184364814CD45A8369AD3FDFFDF4D784EF483E1F0FDF81FCAF59C378DBA5BC2639F83B230A23F41CA4B2D52617E0F237EEB29DC76303AB6FA05DA83602C0C17B921537DA0FAAB07872F80BE4905D42FFCDDF9A3E2CC27C20F764229B77955519B971B0E52939A55A6EAAA74490DEFDF99D1833F60B7BD3EB1D7669680BF43F0D598A85C25483108E21CD1002F96BB9CDAF8F5F948BC8CC10803AAD47AAA1524C1DA0811957CD156EF83F30108ED854495BDB982A734BA01D8B963C44E2921F622A9479F347299C9456146979889E9A1E78E66627806EAFED304CD9B412A505F9DC3014EFA2E85AB377FE8C293F60BD0714165D33E435E4A3D9AC66DC6E7ED54A766716CF5BDE0DA614A4EAEBD68B804CED4F1F64D870F1B34EFC5060D4C7931B4DB876A2EDBFAE7C5454D47C277E2A18F9C2A7E37A3FE01722BAF0633F40ACA29BAE89F1BF78C883C244A806B77DF4E34506962C94CAE54A7C6CD6BB7C4831ACDCBFCDC80F27A29F30731039C403F561B8D99992E3E0F03E6179291DC7FBDF6F37D58726E208D9258B06BE2D142E451DB92BD6ADC3014A4E3B900B59F18016A1C132ACE7C9FCC05E42D8689A9E20653A9918A036B9A74E7723CA0A63C37E622674864DA026C1A6BE275B801FE9F3D83F051AED7DEF71BCD798615FCCD7FB1C9A337CBEFBBF750D586B8CC22C67A298CE2E3828ED8D3A97F00235D8CBD1FAAF0088EF29C3C878DCA87969DFAB3F514A8E265D39F0615AB9ED3508510FF217B431737C2CE217656571914DE975F4D5540CB8690E5493DFA60D7B6A97ACE6AC4A1BB61FFCD44BFCFE093930C861B5D28F4D14D1D640FF2B10F8939E262DDD6D403F9CFF4E6A6CBB9DBBD307BC03FA76A6902C8D56BC553A5C12CE1F0B4485F91488AA86B269EB03BBD16664786F24BDC7B8A959C2297FE059D713FB6971BD325C19A4B1E85D46E043778F4306814597174436C5EB2D8BE05DB17276814FFE495C0FF57DD462853AD61961CB449963ACF54F8FD896CB2D2090DA246D71C977955519B971B0E52939A55A6EAAA744E8C4F3B23A7F4D179B5FB1B52404501707690BF8240081EED3019CD109FA3B5D54EC8C6F9D15EAFD84BF72C93366F3BB387052BA9101D9C1444197BBFE1688195AD746666A27258CBCA7746F6B7B2D4DB09136F5F2ED5EA9D48B6355B5982B6594A11085F07645EB03A32B61FE29527C30734FA275F5601A6E7964A5F8EA2A3EAB3B078D56021BA54E0EF60B00EE3C556AC666BB20DE57E1F899389CD636F418A110F832C18A27929D4FB532C13EB740928EEEB0487263A6FDD4EA63C9959F81FE3C7C1F7DE0DB338A706B010E7C857577955519B971B0E52939A55A6EAAA744EE26DFB48EDF8E5D0999EA64FB807E02EAA7A897CF05A01D81537459BD2CC419AFBF67032353567AF1875E195FF996BFE53E85D5508F70E9F79158397D9B70E48604C35CB285A24758A5BAB173C3A7A31A8489015AF8E740BD6F152C01EB250023FE5823AF84C65744942B71DFD42F657D08E877659143AD61DFF1F4268FA374A82509151427E743557B9F8D56262D29A18AFE294133F60F336D1949320D05E871A4E6C86A0F08CC13E0C9CEA8E2D5EFF6C7618382BA3BF9271D53DDF8837E918F5C0EAF12D293C6BDA9502268502587C10FB1B064C0040F69F42D91EF5971B067452FA91D314E709EA7DCEDBC0DEA29B5CF12C3E84E1D09FF855AB1328BCDE724B03D50287A9B52656D9B5FC4368D8BD68E8A2632EF2B2B7B3ADAB00A912AEDB4789FF4EBE69F0B3800A6D92DD6EBFE6802B73AB166A70F811FCDB939D97498FA428487CDFAD5F3CC1E180FAC5CC7A7D5CC5F44031C99030AB0311F3AD20EC6416C64F13E8D6AB3C657D59D1ACAC9FF667B575AAEE7465520E45DDBE275A2518157C0C655659FA6E529A1E117A128651C0C427C4F432291D628116C2A709AFD68C4B0319F429AFF99D83AC7AF81713D1B1C463B83CFD3EB16B3DE367AFADA404628ECF5DC2728BEA13FD86D9803DCDABC200175214D3942F20F43BC9FA8A678EE547F599C634E04C0F34653FF1B8856DB98752CB4410A386BFE3AA01107400FA934FB393A8DF3F78710467D53BA62FFEF8DCF365D54715FD7A1B6DB23E854614F49FDA0FDBE907F54E60D5081148609905E4B2E09EF4350FA0362AF990D14AE94D98580354019DFF27240CF4D35355B63D4F6BA33AA91AEE2677BB4A38AC9E932BC5511B334A4DF6E88ECD47434E7201747BB3C09BE4440BC47642B08F86FB7AA0A8A319136321D46793984483F887368C4B0319F429AFF99D83AC7AF81713D77955519B971B0E52939A55A6EAAA7442CAD5D698776A2EE9B20F1F34873C3FD20932811169CECC7F3C85495CBFDF40D02F7FCB0A65591941489CD7727B981A9015AEFCDAB2E93E71574C7015E643F7A07690BF8240081EED3019CD109FA3B5D5965E6AD14F81B950EC08A7F1B65EDB0557EE36F7A520B06F90CED8A61C9DC4177955519B971B0E52939A55A6EAAA744E937588583F30F0A4B989077D740C528114EFBF4AC8FA3D928A360474DDE632671AB84C90C2DDAFA48EE69ECEBA987BDFFC351C2199CEF4DCBAF7DEFFBA22D24C9CF29C6B4E460D470C32339359D40C9C40A37A3F3CDAC9E3B1381ED9F21F7CE78A84A1027654278EA3A8217EAC2330E00D79AE2D9FD36B483C27C2D98FC12534CC52CBE0843C0586873BD80538EF3D8531A89906BDB07A5112B4E290673749C653C077FD85E70F718F40FF91F15FF9BD7BD30E051C723EF617B6A8F2BDA143C49D468E58DFCD265E9310DE1E7F377F7E5B35557960DDDD73ACD1940BB1A65C4B32FA9890263339E88512EE5C2900DD468D6934A90C05A6CA7844D7423E7B4A7AD304BDD5D8CF00EC5040030DFDB8B286B5C249CBB7E342FD45EFF6621F5121D1DCA6DE016CA4A124C09CCB8A486233594A11085F07645EB03A32B61FE29527C6F18957EC6A3EA46135A12E91C751CC51747BB3C09BE4440BC47642B08F86FB7DEEFDDB809EE0DEF230B0E238417FBE8AED58521F5C44D171F0B299E2FEB6A9A02F7FCB0A65591941489CD7727B981A9F7BC328FB0A0FDDCE8CC8A4202E7EF8A94A11085F07645EB03A32B61FE29527CA6FE94BFBABB8A21EFE55FBE761DFA03BED52DCBFB01C68F417D70B65CC31F9FD9330203DBC29F316EF1A4E35E3C2A8092D86C2D178B8361A92276FCC8197A2D0520E2E2800E7E5D257DC0CC6AF39F0CFB9B5EC47A19A6DC2D95C76BFE20B9B03202707DC779D7DD4049528135B6741D5C00FB2F706B64A84E96B0EA4D9104F7098D29E303513CD55FAA868D43DD04377AFD502374153ED7883F4B1AE7DCDDB7E216703A22A30EB12DCE6FDEB63729F54C7DAFFB652D2E48D8F041030961E81224BDC7B8A959C2297FE059D713FB6971BD325C19A4B1E85D46E043778F4306814597174436C5EB2D8BE05DB17276814FFE495C0FF57DD462853AD61961CB4499CE467EFEECA3EC033AB96B0556BD307A3981E5E7597FE270C982E93E5C6BF3E008AF925BAD881B25B104B8CA928C15458AC0BC7469FFA16B384556F5D45160F677955519B971B0E52939A55A6EAAA744A01BF36EC12D71E1FDC25820C53A697594A11085F07645EB03A32B61FE29527C394D636C8236ABCA0D24733C92C703662ACEA44489EC6158F99B3E88B960E90EC6886467C203FC948FD48220398229C116FAD82E69BC1C7E0FCBF6565B448F024CB0ACB61CF24AB11AF0940E03D210A3B2B3430C919F1792C033470000B76C6E09AE97095E2F539FF174CFC77517F4AC4B727628A1D2113E7004D02856647A9D97856A7AB3B798E2DD8DF93AEB69F3909D34983E64B67603EA58F56CF7895F2DDAA9C0EEABB040412ECD8AD9D686EDB186EEF992F24AF9F4E0BBB48EC1260AFD86F05F48783456D28C6FC72303BA1291D098D7A475CD49C0FFD839EEAFD92F93330595D7DA040B8EFFDD27E70743598FC510C3FF75F4C7665F153EBA393626E4F11E6C0EE7D481CEBF7016039B6F37B9918B9315EA8A5FBD5D1DD0D380C0AC8CB6FB5BB68239F07D9D9296B0F57BB5954BFE3846C46CFDCA0154CF9E59F7483395B1B20F79C224D7F409106AB4EC78FC0D883A73C619CDE930F4B5B45EB20A0F29C6BF53FFF16010EE0E9917DE15B3B0624C553EBEB82D87F460F9A573ABB4E5C5E53D66C6AC5A7742BB34AAED558AAF68ECFD0E7B313F69D92B9455EB7FB8934B73B06B9F56B57A9E6249FA3421430E8B7DE0E3B6EA0B38ED84E6433C624474F88D088770CB3A270F93738A8660EED28F7B26C33DEA14269F6E5CA79333107EC336423B75D125BE0363318B8B433B362CD3AE62F42CA809C23F529BF43C6ED07B0D8196B4B9B66975305A3226C5CC4C2E7F4145F6E5B20A590FE2B9D42629A95E06ADCD84272841F18303F474B054B8FE3399251F7DA19FEEF2E9BF5F533219B91BAF2656C4B0FA17659B11E114D6C2747533BBDEE382C00F881EF03700D38E0C9B564B5025548B6055FE60C035C85594A11085F07645EB03A32B61FE29527CFDD06961DD0F313C4B64A4E7B616F12BABD90B3851942445DF2E8DDA4D2C9E92A526F33DE27808D086F797AAE5F8B26BCE02CE050555B48621F7C453FABD0BA1C403F561B8D99992E3E0F03E6179291D14762DCEDBAF5B00049803F5308712777F16F61C25F85BB79CD4C6AF9B657119E970DE64C6AD6C7FE7F1B14745EA2ED1BF056F333584FD98D883F0F68CE0B397F304A4C33BDE9E21F1C8D21E1E370BE177955519B971B0E52939A55A6EAAA744F7B03B6636B85B4C227C2AD90F28455502F7FCB0A65591941489CD7727B981A9B32B69113650BAE4EC2753C54C01614E7EC3B1C968DF606368608EEE9AFAA0F7DB06625E64BE0609C490726F2DA291F7F49906ED4504FBDC80CC3BA5FFDB4CFAAE7C06E18856A48CE62AA0F5CCE999513C0DA2E91755A089DD189AA4D61DF8C72D68BC823CFD9B5106F1A2CBE6E2704A646F403C5D4FBD31B24984E1D357BD9E3BFB8A919F4B4B12FBDF7CBCC91263480AEE3934D757EE43A3C86BE482A49BD8127573B57BDCA4EEBFB759C3A40A40C5411409CEC072F643B804C0D4C339D5E6BCF421CBAC85AB9B81FA802C91E902F30AFE8B736F3931EC11C6DB30D926CBD18BB012AE427493EF4A0754FBE891CA99AF30A8E12F28BEC0E61CA3B9B99D59228040B5EE1C9DE64D1B1B66239D6C1C6BB4335340D681B35D05534EA15B482CB35949514358ADA32009C3AE92A6D20F5BFE79A3147026318E10225D2C9D356B85BB1E200FBFE9DD30A3F160FD25D91FCC0513986060CC0B785A55D12C36EAFCE8DC0EED4FB88F9FA4972CED5D79C090ECE407CECC087F9D62B778F9532E63298BCC37478A4264CF16FAFF6ED661D3AFE602F7FCB0A65591941489CD7727B981A9EA4E7422AD5D258E4C40411C9B709257CEAA4FAB862DBE2677C4B06D7BA161C58BD9E8A3A9C2D961CAE4E1E266A46749F74A26D9AF2CF144E949D0F09AB61DB802F7FCB0A65591941489CD7727B981A9320A13E6C31A435F446B87754955978C7BA5103570240FA4242D8C1E77BA99D894A11085F07645EB03A32B61FE29527C872D4BF6D24FAEF3449A7DE7E999DB59F4A348A1255C7BFDFC26F49C68713F4294A11085F07645EB03A32B61FE29527CFB27FFA7003BE08F96F6A40B7623AF1F86C216F21CBE5FCF9C91A334A1EA9EB0AF47E40A0B417A1ACBD0B1D1D1A2DF91A06594D8423336350E6A8C76D029C4D3AF47E40A0B417A1ACBD0B1D1D1A2DF91516D57FA491921516CBAEF605BD08090E863996D5DF73058CF3CF153708A9927C40A37A3F3CDAC9E3B1381ED9F21F7CEFCF4B5966E8001E796CFC9A898FD1932B3E29D662540AFB704900F2AE81CEA704684C9388A68F182A0CC0002248D52CB11C37FC03395050550AA37A2616DEDAE2E0042386789C8FDA45D48597ED23A1F949B25747B0DEC43F605DA11796480DFE4613D47BE536FCB57BF2A5F07FC8A3A142E451DB92BD6ADC3014A4E3B900B599FC175084E735F135F18467DB19BF397A1719B8089078CA3F247E87EE23C9D008C713CE7B0AEF1999AEC58AAD334F1188379653CEC1964D8FED2A907B196E7C194A11085F07645EB03A32B61FE29527CCB0E38140D1B74DF0D9B332E79F7ACB45996C1C6E206A2258FD169E24A8E8CE19A0D233653C9F5D962F49A415A9C690D40BED0CCA9BBD5AA2883E8796AD932E90EBB81CBFB39BB02E08AC24FD9B4AEB85BE67491C504D91026B2A67F770DC8C594A11085F07645EB03A32B61FE29527C8045FFBF27F9C4F3C37C2F348499D20894A11085F07645EB03A32B61FE29527CDD3A9E07D5A46CFFE45ACA668FDA27C194A11085F07645EB03A32B61FE29527C7C084B295332A48406892597430A101394A11085F07645EB03A32B61FE29527CCF24B3BCEBCA5BF6D6D7AC1F37E97BA373ED797A94949BD144F20A913A7A2C9D0562303813636F18280E99B8632F399EFE891A8B10F21BD2F1D8EBF665325D49A41D286D4C5CFB6B3D88131BB7AFEEA5CF4116DD5C3914608335792CDB3B7169E3841454D79C59B9A5606B37098E979C11BA3D25210285B09539743E8FC6DA8900AC8FF5CF0B5D838C532FB804211FCA5BC7C93E0803C0E49FCB0312A883C3E069E8169AA98303884CB6C3FDC7D9153157A1D4D1BFF387B57CD1555F7A0C72F4FB7F0DB5EAA8FF791F635863FDB7BD671FFCED925272D4D35F284F37E4A4E3357FD34A01B379DDAF209E2D4CF5ED3D4DBB6FEC3BA80D0EB4807FEC1F1BA3CB5CEC2B4A776732A7E36F8DDA12ADD28473CBE6E7539A0155CF70E7CDA0B0F6BB08F99E0161BC8B1566B319400B0E119AAA57138B26B0CFBBB0FED8FDF26D8FC3BC7A899B60F371FF4154192854D9CAC31AEE0793C8498D64BC8CB06FA021E0FB865D323ABA26DB5A6A742871FC1EEF443D7179B325352E5AC4788481B2A6E1FBBEC02370B22E4E649D6542AAE25C270F1A74C078502270C88F0D19309B97705230969609CF7B711DDD4D515CDB8153ADD6A418AE8D1223559D56D4957117FBEBA67324DB52A67C4B57B00D935C6CAE79705FA8317592E1405AE491840338BCC62B02F7FCB0A65591941489CD7727B981A9E01561CA2391BF8E34F2646DD263ADEBA674091AEDD376CA674B457A8AACA61ACAA188178680E5194C9B7FA52E4262DCF0FEE50EBC63B2C8A1B12F5C5359F322AD566BBA51B253A8170E26EE97D684E9CFC97514A385EB93DD989E1AA1E0C4621F4181E85935E26DA85202530B6AF3BC3DE4221930CFF59813C889BC239EF77FC84999CC5B2E62311CC4415DC7510AEAB26209B994B55BD6EB89CF8ECB87A2CDD5C42E0344629B7DF0C599819307E1B3240E86B6BAFB44B8868894B344E958E9257C246B5E07D9B06AEF3ED62562F2753F22692F1ADC582DE6D5ADB0C291164F9A95ADC280FB835898236EEEF254054D5D5ADBC0B8228CE8508F06311632A776478F2B7044DE266AF9CF8B6EE777E95A721D775CDC1D0007E187C6562F9EABC936AA03540422B3CFD2E458F1FA0D170F82A637ACC809922C9837C6D298995133BB326F17755F47BF7C59D2AEA5094FADBB07F2F9B6D409A0EE17C406025BEE4186287EBA1A5B4DB347B1C5B5FB1CE188396CEE1F774EC4895CA9F4FD7B1CF269394F8583064EF15DFDF443170DD0C2C9A1BD5BFFD82A5A8AA9D64B1B742BEA8E845D805A82AB369D6E253A71D5EE1DE10B0EFD5412C04749E511EAB080F83A67C1A3C8EE9C89DEE11541F6A2285E936EDA2E6CF8804DC1E9407D15D364F4829DD41EA71D19BF2FE9390EFB100A67E600E361370F87415AFA7C5D8DD3C252B9F20AC3E4149AE9B6AF9160BA4B9E04E83AC81F9AE1F1341511C0474D04222873F3498EDF5A54BC69F035E618E1C6AEB5ACEA76431B117825202D954A60872B3F661BFEC2F78526260010FC41C36CB381B6827831DDFD9D0EABC84769EAE8ADCCAD92A08ADFA74FF1DAE16C03079378955A6E39022E6A13EDCB0E5CEBCE8E52672120B45282B3DD8D2469BAEBD5A46F9541AD2B1C9CF542C066E3824364E48CFC47B202943631FAB79BA379EF7DA3B2936FE27BB19467A0C5AF90420095E6E18129458C30C879D1794D3C00EC17BDD26C6BCEE23BD88F3C100BBF1AC6874F60C1CFFA3D48031584A89FC3FFF09D5D63F675496A96604B2DD6DEA46B40DAAD887E19DB42EF62F9283AFDDBC48FDA99CAE5F1D41112096DABD90B3851942445DF2E8DDA4D2C9E9221CDAC11EA02A1A133A2650541AF724F0BB0BD2A4D427CAA857DFE82645BEF1DF5B935B1B57F9A686A54EFFA95E28B8510D009B57782E0845E10B99B9FD7A46545D0B8156FB7C5464014FF41F49E681C9F86E480AED2C88D667EE8225B03A390D47BDE505A99114EBCA79D59D56BBC4755109ABBD01EACE19186F857C2A8C03C51BB34A980C4CE79FD9919D14D74ED9A5107BEABB42780621CEF4385D09CE105354D1A035F0C6B75E31D2E78767BF848FC5242FF30EFC7B6DA05BDA2D1392FBE923EDB3A4E4B055A7171D96336A130D303EBBDC710D0BD8F42D6AF10CAEC2E1A86A0462DBFC732ECB85CB44309000A2732B7AAF0390EF6E65A2337ACBFFCA69B39021196DED7AFA1B03DD7E83A023623424E5A71CEFEA4AE86A0B1DB314669FA3B2634629F01C7A15A03108084C2BFF335E7ACFA0681219AD8B7DFDDE63C80B5B3D9FE83B6B03F63AD9AC4C732617D0EBF4B362F945E82CAC9D399448C28C7A69727E3569224741DF56D26DF00DB17ADEC07EB2BDECA872E90178838AFA552200571B958FB17D57DBBA721EE1830659D8F66CC4AF8DA1AAD23DDA7AD1461F01D3B03FB9698A28DF0A5D09144331ADD9C5D909B8F9A511A2FA3837ADEC42C25D7052F1F3BF0DC47768BA87634A36CFB7567642A5BDE90D39928FD610EF6CF7553AD365F0B2612F8BED1E2261676B6863514097D309EB6734BB1ED45F6EC261AC5D0EB0BB31DA966BF39CA58795E36C0B328B4F44E7206F2A604A80D25F49AA6D24A67622F593A01172C5F639C877339E46A800354A14BA1FB1038E9A637D5502AAED88DB00BEBC4D33221C0F938317209B4B413BED072290489D09189C63B7F92AF3D3EF93D122A6B0409C5CD5206B2F567A9037F46003DBB10EE21B5B34D3DDCA937E4D838EEFE3D5FADDBE575668181048B2D366E13FFB8C48FE47E8A3505CA6F27F2C00ED92A89AC74E3725B325F3A0BD2289F3FA57D5213F8B549D94381DFE4668FA7BFA882F7599A2AF6A6FDD962A2F53BF7D4C5C43456DFFE9A8CF9BBEB313976E9650F5C72EC46AD81A9C3146621A52A4BBEB9A0F4FD6D2D0F5665A6101210853E25E188C09D80AFD1C2C7FAB268270AB2AAF0A7D6E40918F2EFE5D2BAA18AFE294133F60F336D1949320D05E8B4D1094CC8D43D5778B5933266CC43FA41DB18D3E60057E7D7424433FEF0A7A7125016B1CB289CFE641BEC566385259807690BF8240081EED3019CD109FA3B5D3F0C0A2DFF28F9BD8779399B5B97C6F37140E0CD88E56D1D4F4E365A874281FC0EF4DB1A154DF8766F16633F5D501580A3A69161A027F983BF9F9EA42E0FB2ED84B1CD1A09C849E268A80A47F8E1BF5935F0407D997E54F731EFE2EA0FA997B8D0B69324BE2F93BF55ABDC488E0088F89606347464978F68FE62B8845C15DAEECCF58405389877F0B6EE840606E557907CA8E60F1CCA3F46B0EA6D0995BEB51BA57B03D9DD0626A49F8AB1288E2079FA0F0C5131D9756C8C9356DB1CC675D37A7DF1CA3831BAF1FD5945762180BA52A910D8D9AC89746F816BC4D3B697B3159A68E1A0928D6C2C4F985C5CAA1C861BE19A0D233653C9F5D962F49A415A9C690DF08C4842A2108CBDAD961C40B8D0FC811FCB0555053A333E905FA1D27C4796BAB6194E2E5C85C1864BD0A70F9C3300F6207678318CCBAD5FD55DF927CADEFD370EF4DB1A154DF8766F16633F5D5015807FC63E529DF0EB8B8D5E09FAFA2FA3A702F7FCB0A65591941489CD7727B981A91321FB1F424BA934386F6852CF705B1621F703C34EF202029A329A2679E560D383F5EC836F9C30A78AAD5F45665C5C311286653F8682979E00DF3D8DE9D3E761D9330203DBC29F316EF1A4E35E3C2A805B3F843A3A3BD25A5A680047E0B4655FE73147B3121F93F6B60407892D4A99ADC3265FD6CE6D4086DB260EF1DDC031B422B365698E0D264DA76B977E0BD0CCFBAA80F426229C53A7ACCAF834674CC0D5B833AE1A8B6AB8C6270B30302A1D1A0B9BEA926C9D58AF4171E5C62725F6E9F83CBED7FAA7B47A2C014F3CDF9FB3E5C12E0042386789C8FDA45D48597ED23A1FAE4401EE188D83D32D1C4935464193D47161B838FC9A901A04C481D933C8DD6F8FDBDB54A8834467532741C75D264860EA5F7456395233A1E5A0EE93290503B628E140462C699C68F0ED0C1DDC94D9DA2AA2D6F8403BC16F03B45DB889E3C57C8801D6153218F9E9CFF4A46EB3C470BE0A6734249275DFB3D6B47813548441B1D2D04B20C9F9E4D7932C14F4CDE8EF53E76D193EB0CF134E9C843A826CE361F9A49E712DBC4F36F9F78A9CB250B9032D35822ECDE1217AF26822304C44B12062216F750E18AA6609A132E96AF44A776546E431F3181563557C7E9632CF1031D33EF833D7E51A8A8A4A2CFFF32192C52DFB1C2ACA30F7EC5B0DB51B03840C2BB966C7E9BB5A824F789533E6CB9E16425DDA580F77B747F9DB25DCBBBCCEC2A6D90EF4DB1A154DF8766F16633F5D5015806BE6BEB287D5297B1BBCE463946A195CDCE76CDCFB0FFEA5B2A3CC889AE54AD3D8B9254327E5FDA6CC43562898F861D21298EC7EB7DCF1523348B82B055A055F1DD55B0497AE6860F07BF2DB7AB5B99FC336423B75D125BE0363318B8B433B36EC0D287E805DEF355F30193060DCB9FF1BD15565F799477B0CF15BFE9A6063000314EC0EDE977039CB2BBA790C0A9B53876C8D4EE140DFB2C12EAE54A2725225ABF968223AF4489DF2AD94635A55E1DC5267D80BE8691AC7933976DB26F72FF8E21F795CC085397040077A6E7EC97013E5564DFCAD048CD678661EF08AE6AE028E4E7AB7CDBFEFE5D8CA97C5B1FD9DEA7C7205911DEA120C5328E5610A0F62BC8FF03633D8E1B38CAF05E20ADB40AB3A187DF321EB68556D0B9AC60DA842590DE913B94B4A69D9772F58FEFD86AA4B2265C37F653DCC935E683D30C31A78F8DC89E88B2FB93C364CBE0B5E9F76B3C9AD1DB3B6B3467639B93051FA0A53D82355FD6EFF63849D92EDEDAB03FD065D25CF64DF8B2439EF36A03927E54C2FC545D3C83329BD658FB39B2B6CCAD2DEB0E458CED581CF1AAD07602092B32BAE60F3713652E6CB0F6E8D1E2172FF7A307CCE7FCA3F0B3018EB970028D83FACFB0949A53FED00FE3ADBC6AC58A7E86985A6543BCF108582463FC12784CDAF58FBEC76F03A2752C13EB82742AF0C6F6E197AC8D3C336423B75D125BE0363318B8B433B36D4F220B8345D7464A5268456D3C3CF050DA49437333BA4E66CD76147F46336DA4657191A302FCD04546D262F66D200A1C97C86150C6CB051D744A2D2D7F7EC8EE6148E895D5CD2FFC21E208F6E676C452C8CA1A41B063246A595D6D82F3B68D0AAE2B8B16EED703FC881B71C1EE06A83385EECBA6DF391F892B2368EC2FB59CA6601AD5F821DB53BC08EEA8F1DBEE9B233B57EF4941FF46A41299D8AEE52400043E4AA1DBCB1650529262DB4796BE48F9014EE0798321439BC552B42559B7588CC28E8E255669F33F375D26FF98806654632C4A661D3EA3866E00F9C23CD51E31729476347748879FDD8B7704F80420FFCA749AD9DD6BB9F80A0D9AA953E9B03F17E479D7E92EBB7DB60C93D4B1A26FDC8E0812694CE0A8A5C8C2305E6D5519865265E776528B944CBA67F283858154981FB5AEE9F8057581065C8A8F75886964C3BB5311ED21E173B3EC11FBD0171946C86A77C290F55D27D6B23915837BEE67E2D4498715CB4E4BC4AB2CAA43A4555A874C6B60800E4972901ADA61226C287505E87D216DB31FA6524B4A8629F95D88C35DEEC70E63BAEF917B7119BCDA924D076BD339ACF3054217DCB011973D0A664D8A6C38B654A4CF37336DAF2A9E45DD81E01481AA205D1287903F849507EFA63ACF54F8FD896CB2D2090DA246D71C9C3EBA3A06FD856BCFFD6F4B75BF7BCAA3FED00FE3ADBC6AC58A7E86985A6543BEF6CDB8AE75E457610359445BD83EC95D26A76C43B5AA04C7F6BD7350BD14CECCC28E8E255669F33F375D26FF98806654632C4A661D3EA3866E00F9C23CD51E3BD532BC8CE0E52794E132AD97A65FE95D502C649953A58FD8B9AAE7C233FCAF092A35E278F83BFC662EE5CA1D0EF258B2072D38E7CD182B208CD0DB1CD3F5EF6467B4C621C54C095AE7840AD4DC084981D5057A473C1A1326389CD7883761192999CB5779DEED8C9BBE8A0BA9CF2644B83735091CBD6B99CB62F80A175F767AF17DBCAFDCA8026E0ABB380AE9938E8DA9C0DC802B4168A2F5F24483830D3B31203677B4A035E7B0D828EC76A68DEED70974142781EB8709CABDBA967E0D9750AF5423C4F219BA82DA61169F0937215FB9EBB97C192032072A01292E7A88B0F8368C4B0319F429AFF99D83AC7AF81713DAA43E7EA6823A13E3C445080644B8D4D5A4FFC05CF3C7CD93273C287BC3C7B61D8ADB7FD648D75B5179516DF792006A70F0E510C2E8CE00A5EA084F3CDC5ADC97ACB3316730C31658425314471FB57BF852279589A9CA273CE2FD5C56136B11C7B7DF38E5B0FE09BAAD132D105B395DE1D94076D707EB8A970BC3F291C5CA784B2CA51BA8C7FD32BB0A26A0AD45C811E9BAA30F57868E734BF72594331E00EAD746AC1450601CE95C29C5293279C89C13999996E7298372D17F2DC5697F2242F0F6A26AD61F67F60678151671562797CC30076D7C3433F6FEBEE789B0DF739EA61A1A8D884B0A4CD64D87E2A9062B99357F58759716C07692DE093D90338E1D9E61923917DDE422B536AFA6580D87D2F745265C6BA8099C5DB51C8BEAA5E2CF5D974143D5EFBE4EBA42A941BAC54749095033E3D31B2EF8E2B41DA36A7D61325B50D39E7CE91F0CA3B6F422A6B813DA4667B79376E820BE711A34F6DA54A6B921B1A30B3FF22CFB32A83CA5EE0F3F94FA0D03E58ABE36BEC05260CC4E5A77C83015BB04ABB1A7641A9B0298B791F7E28371BAE546B6162975D938737DC5A3DC6316204F7F51D3ABEA111E62462A887FB4B746100A707D9D5C6E300E903CDB227F28CA6FF40ED0A658A5020E1D89052A4C2C9F2382035FAD667FFB5C2BFBA6BC9A2A11DFAAD5C2679DF3A230E6C90879D11FA136A38228ADCE2D7A56A1C194175E6D7EBC310DE31DE4C327E5E7B2D1F57F7C3E9D04838D25B04A68862A94EA076B844A6635835B435A2AB68751714D263A92C673F7CAD73C02F5DBB040037885751E81127218AB7A69E5403A1060B5A6AE6CF7E04F82B9DFB5A093D13F827BD8E4F1B758E9764FA921BEBBAAF9F39B7C3DB08DD4F8E8C27A3EA3CF7C6310EED53D005BA3B8E8AF82D74964A6D88207F8FF654F1162A9CF206F6139F60369397DC2FD18B9EC99A512B49474805B842B5071C3F962DDD801E9291E553A93D9AD0AEA6EDE03806D980BF308113A210C91F935579296900B5B1AA95530153B691FEC9F3863CFCDA88F976DD111F77992A3DA12CD01F6814B91162205D8EBA4535D2DEBD2183983A0B27F59604082ABE597041C1F2E858735FF382BD3D0E239450DECA635535F5356BDBC4D76644F307485352089DA33B82779791B6EED268B034E76DBE5E850595FFE0A346DAACA1A3BEBFC75BE1564A6A9E8094D7605E8021EA20649601B98361E793C62BD2ACC6A5D6E3C52025CB35EBB425AD3B001CD6FAA542D8432BAE0148D0A9EF8C4ACEEB5D2A640A4C5EDD01695EEA92D40B0664FC1EA05D64BFA378884E1F7EA43E7B3BBFC52F480E7231ED64EBC994F0CBC2C937B5D6FD74311F7ABF7A81ED042276B525E5B2C47D3CA2D856E61DB0BD364A66D10F5C9152EA6EAE3C2AACA0DE40EAE1C30722A06A38884EECF9CE95B4C7721E3572C47F782A5B5C75A5D4D374395C8F2DBA59E00BD2289F3FA57D5213F8B549D94381DF64633A491780A9A50C950A59ADF990D844E42ED80D05BA26A38D2623190984C44014BE89A7F8F6A88EB527B24FBCFE607BBCCE735E2E0D8047969A3BFF25128B6599DCD6FCFB219082DAB7D57CD16A6D584D3F3ACBF333C892AF9E69A3543319F0F0983D2DF69F2DC06BCD24B4BF0A4604D6AC865698491C06DD6E28B99077823D5DCC28EA18224587995A116C5DB8D6580C5C528C9EB4A6A944EDEBE8F5029EB590D4B67C39027EF147E955AD9C2641355DD3F1BF90F69CBED42AA6C9653025B874AB32040BFEDAA511202D4F8B46D307A3B7C6AAED4D9BCD89BB35C1DD0C14ADF9E56E84788B6D7EF98CC9A9C82701B0DBDFAD92CFC867FED418B370605C6102F7FCB0A65591941489CD7727B981A9C84BAA75457502D397CB71241EE3EB49C20B9857352667E6004DE64BC9D6806002F7FCB0A65591941489CD7727B981A906B08985CA1CD5646D2208AA5163C8A557D1764112717D26803D541217330C5B0414561040BBF6A5E7BF077E850B55BC4AAA4E20568474253187F30D9C1C099C850DA4F2F41D90D6547DB2A53DD16CFCEB42664BFD471A00BD2C73B50119ABB0B1941DC74E2EE9DAE7729CA4DFD56C1D67CDCD14247C1670ACA4DDF3C9B6B9665FBCF95EFF25D69070F897009D80D4B502F7FCB0A65591941489CD7727B981A9596EA4721A298975C3D4371EB5B8E7BD0A8A92640422425196615C8C53C443CE8AA5E34FBD6ED314376A2743932A9D87544ECABEF060E9ECAEB58EB06C6584429A0D233653C9F5D962F49A415A9C690DF08C4842A2108CBDAD961C40B8D0FC81AA3FD14D8D828B03CA96893A3D19671F3025F8417168F7088CE70B7B5A52069330A1E0937752A7D2A7E4B0945313A0E7FA6A2D3EC998E68662AC037FBFFAF0DDCED581CF1AAD07602092B32BAE60F371E21E4ACBDE363EF2FE9C194EC71E48C95D8DC03AA012CBD583BA44BF0E5444F172DD47E5A6ADA03B40D884D0D54671681B263AA437DBE5C2B9E8490CD26F3C2FEF46FEC3F626AA32831BECF9BC3F16568BFA798E95052DCA90A804FE79CAA02895453CB17AE8433576C1A9AF05F83FAC25FB0E941CD8F09AE927F37D70B0F28D02F7FCB0A65591941489CD7727B981A9C01CB65ECF4338777517367B9D76E81177955519B971B0E52939A55A6EAAA7446954D35688780F09BA71914D9E70D18EDD7B2E149B83028A6CB318DEB18946BDCBFAC860D5148FB84B57CEBE9DEEE4EE7C4831ACDCBFCDC80F27A29F30731039C403F561B8D99992E3E0F03E6179291D834BC4D7A85C55690850636BF57DA46A403CD181F58AF86CD70EF98BB7DFEBBB416B58E66F3DEF2A340F714017D6439F4422E0025A6F6F3FE937306DDB7C25B2A6A91896CA241736C0651268E1884565F8C54D39CF395714A0816595E73E5C008DB304D5B74D8A988261E52AD0DDBF688A865AC3B974CBA8BFBE516589971E02758718B86D6C098908EC64CF691E049C8540EC519C0F6230D50F7D5590AFC1B7CC3C0C1B07782B631138F021EFAFBA684B1D3E516EDAAF0398656B1FC4B88B34E4418691DC72379280FC4ADB805F007E1CEAB838534BA40D61619F15FBA5C875D4B8F5420377E7A22A88B78298FC29DDB485874B12F40A1634A0DCE789448ABE99FCEFF58456453A89D306E21469C2B374D684F75194B457EA825EC19F72F8A672A92AE6FE40B7D586EAA6AD522AF5065863627AC79B81DEBD12E71116177E0F3BE82AAAAA63C06FC282C023210BFCC84AFD474E64D27639488D35AF21C526FB40316EFA4FFE8422B38EF5943D5E96FB7386C39AD98B8D10B99C6F9B25A233B35A9164AC35D42B1152D4F42D071AA8820CA133E2A5D7257E1538165CE0F2686D7D4DE8AAB63446258DACA3AD1F15294EB5E15FD22B73C2F73E001D1B293DC1F770B6B9617AF5851C0232376D42506E661A72C13D03B78AEE93A7EAAA0CFE6935769CDCF1995AFFBEA094F2621453985F5D5BA281C74227D6BAF8AD822C45E3B0A9A65C9F995AE40B7BAD2A969BA31B0BF791BCF50B34349BEB07B922DEDC5982BE5AACB0D2E2391629DA0C3EB6AE3854EFC3795DCB7AF7EA43A64D1B2C0742BFB6DBA7A62AB01DC9F4A2FB795C369E8E4EBD84378E28BC13B92BB15806D2754B7C4831ACDCBFCDC80F27A29F30731039C403F561B8D99992E3E0F03E6179291D6507794B2505E84EE8013E40612EC5AA52FC73F152A7F2E2C9B9DF47B8A2D98C907CDD2061EB77713B1F99942207D92F64217523B2B73DD89ABC13C7B26492A3ABEBC0067F39DDFB94C114A5B00EF2089EDB8B9FC7BE43B8D0546913FC32EFF2CA3F0B3018EB970028D83FACFB0949A53FED00FE3ADBC6AC58A7E86985A6543B1291127B80244FEC574B94FCACB514A2CDC95FB8FAFD18EFC14AD1D29D30AD8172EF771830CD80D1EC1CEFA21270F4208EE37F4AE53A5B9EAEFD0C824D6F0AFB1D20E4F359A8FCB0DC7A1508952C1B0C045C449805F6A0552053F24AA1727C6B8E6899815687D5D03E1B4F4BE57AD5766A770B5A2EB9B8D61DDFD326B26162EBB5399F3837611F4FB0C1F1C915EBAC205E06ADCD84272841F18303F474B054B86DF05272F7C882ECD6808C61AD403597E22AFA59E41277D19F75BE7D734503013F54FFB8E91ED4F1C66690B132A6F95765F98FEC6317BC679E026BC1F894B37586494B4A6382578698EBDF7AE26D740CBBF063F0AB9D5ECB0509D10F668B83F254F731E4ED3730E75420416988077CB75BFFBFC1B25ED615C8BB3134FD69346000F5DCF08121D16B950F53112D5B5770A836B458CA037FC5B9A6A0FA7D0E0DD894A11085F07645EB03A32B61FE29527C4A5064DD2A10B96A59ADA8C33989EAA21A84314A4FA3CFA267B8CB75FDC4D3EBBE78DD60015A76B91B9D96F3EF2FAAC65A4E9413C36B093FE577F50598421619C481817A0675F36E1B90DB899FE2269FBE7B9CAB9E215508383BD9369205719FD76D93FFD3F25AEED04A87D2B4D46CC79CCBDECD48E2EF637707DC593A1E11D9F691A50DF3653EEB0987909E1E3558580C47F44AFAEB4CB61FB37CBFC05DE5D417A90F6AD49C5121892D5661E290256152FC73F152A7F2E2C9B9DF47B8A2D98CDC6DB0FBCF4168E65F0B433E8BE2886EF030DA36FF62BBCFB93D49CE60CE5CB0013BB4EA2F31256957238CE41305D595F65BE655D76C7F104EFD12289AE3EBCFA2DE16164DF46159DDCD399B7AC373D527E77B6871B7D5E4F7653E426861C8ECB5D571D4581BC90219FE0B1C7EDCBCE9BCAEF232AFF3E9D801EA418F5B4E77B4C24EEB7657A69009F9C8B7AA5C3F6755D8E845B1A04686F27CE3721AD4A901BC8341FDEBDE4B9ED0AB6C4002A3386462C2A534C5148AA8F8F663B23D8F9D85FDD6BC67D1048DE879B19DCF56E43639731751BAAF5F36005A432CC7B5174652E41B80747A93FB375ED9641883E71CC5ACE13BA04643BADE35B0C4ABABB8ADACA0A7918C31EAD8D2D839C35BBD0879636EB337E3549227A9780CE27D7888769B1DBA26BE2B6DD7C7B8918E9C52A6EF5F37BC081A56EF8F01AB93E9E0001BB29D292654419B5D313BBCA6B7620AD14AC364B37BB5F15AE07681EA720937380C294C3422EE8838FDC0CE0FF4043D8023C8B38ED854495BDB982A734BA01D8B963C442A0F0EE3B18E327DB06AA22E06B2FDA115E05550314077D3CF405B0B770950A794FF8D6C44B99EFCEB51998CEED8052BF012E56A7764FE69A0A84ACBE85F549FBBC4E8DA632FB7E70C2035C772ABD52954E6680242CFA8E85D0AC9B3C228D026AA3FD14D8D828B03CA96893A3D19671FD335FBC1D6372ADF051A9C1ED622E6AC3C79DEEFC0CAD6BC738ECFBEC86D845ED968C8F707DD1CA34A3679A692A9BA1D8AAA208BE2511F6CD6B9B24E59BF342D00A3F5D52FB1DD1A8F1D63865D7499F57C000298103D6312187A2FE57D6C91F1DED437C5DAD50EF1BDAF94D7F94745F177955519B971B0E52939A55A6EAAA74423A9685B43C4874F983F92658086CC6046F7B3EFBC3675DBB71F9127A360C4BC22C33833F3846A413AC79412E984BD2413A1E59DF691CB46F76FB75CB277F3CCA35154AB51FB65E64B5D181A25BCA532D6BC67D1048DE879B19DCF56E4363973B4AF7280D43926E10C924283585461AF00CBAD34A70B81C3D1A03472021A24B1B89F905EEB0BBDA8B4C4FF093A132C42AB967CD8487A1F3DCEB8F530E3BC735894A11085F07645EB03A32B61FE29527C0E1F9BD9B2BE9A774A69B1A31BBC6A4CD4DE9145319FA2EE4C0BA3364D3A2D10D7B22248F3F95CB4EE8284A7674833835A624A231E490DFBA3E7A553AE1C9A07D73A7279C10BA978FA9ABCF789D29C851286653F8682979E00DF3D8DE9D3E7611DD46D476140E3472F522914100365BF282DD2935220B3A9CE6057DC86F9CC331B019AF36EE99D8C8FA942AE7469F335B3F5B83A073A318CE695F3986DEA103FF6A7880C4EB5C08817DE703ECAD3F10FA9DC1225D1D5AE0A1BCC7D9FF7F3BB0B21ED55ED599CF936BBD88A6A63EAB3808156E0F1490442D8A40390FA1E0678FB48C7F12B8ADD9C5CC5E8118A8A61DC1602F7FCB0A65591941489CD7727B981A9FBC6AAB8C3AC54ABDDD455544C21CC0E355577E8E53A9BA703D57B83015EECFAB327F89BC4EA2F40E5EA554518E2F3BE294DEFB114FDB6A1FB6F2A33A2BB1340423BE56A6D1AEC04E92801C143755136920A920683432455D6F4B13EF828A6D402F7FCB0A65591941489CD7727B981A90A9B7EFB4CB7BF37CA0B4D505FA04251F1B620BB07C5CBA3691FF15F7A57A17AD4A43883B21B3C6FEFB8DC11123B4B4A02F7FCB0A65591941489CD7727B981A923A9685B43C4874F983F92658086CC6025B793FBDFBA572090556B432181AB57B387D76B90C57F8E9D8AB35144801B3C5D8DC03AA012CBD583BA44BF0E5444F1A3BF213671B9548EC2D4E9A885F1A4870C01A44D99A0F1BFC3ECFCB2DAB0C5533D9B44F5B64224E65F2B6233351DDFFA8814C110F85746FFA7C84B73CE66C3CEA06594D8423336350E6A8C76D029C4D3FAFE16444D4D940D940B696200629F5DB521CD444DAFC01B9DBF89641DC5741883A0C40DD62BE48EC878E4133772F2125DAA48676F047999067583C9C6722B7156462817D1E13CFEEF314229ECCAE38602F7FCB0A65591941489CD7727B981A98CC207739970C8F2937B4A07FC77ACA384F054A17C2A6BCD0418379393223D743C06564982B14D753CCB7829F134E6F09557B191B95F66E76426762CBEE31889FEF3E74932D04119DA3EC992D336C365D6BC67D1048DE879B19DCF56E43639739EA1F4E096DE9FED3F7E27E77D5C59EB02F7FCB0A65591941489CD7727B981A9D3F3FEEA2A9DC17A268BA148D5D835025B4DC7120E7465416BB44F2B5858818C4D367C712CD1B829EA1F9C4C848AE583F317157D32303D2BC584D6A0CE67357E5E61994EE68BB0D56500008D776134575878FFB80D412327651A3EDBFF4BC6B5125016B1CB289CFE641BEC566385259802F7FCB0A65591941489CD7727B981A92A0CF474606B19AF455B0FE76A74577DC9E786D0B6B4F42D54050680EDD032F420DF431912079BBC9B1A1A5873BF7AD259264E5132BD8218A6E21B83D3DF3937B250E21B3F7DC4D8232066990765E989898B0251FC925DB20A5899FCD65AD45B811F5370F1038A6A48BA678ABF5DD0383B18C7D7295C19FFEDABF798E5E8BA255B8B8E52DAB9BF0FDF5977E50D366E9B99D524A0F4C618ADAA0E9CB9D1A002F26523E108BFA77E3EE98BB2A45F2B31C577955519B971B0E52939A55A6EAAA744D7BEF2E2BF0730268D6346C36A7EAF803B980CAF1454D8CF356DBF8AAC8C06B78090BF49A48F81B5A893426FE297CE2B7AE570EF93EF0BAE0B417B89A2D5ED8DE9ADDAADF40D3E576D532B4D3267B5FE1B263AA437DBE5C2B9E8490CD26F3C2FD277625A9B58528E9D31E7921B0B443536574806CFEC4EA6090F072E480AC37381A75E6954C0171573D728FFD4370AA6D642848B685185EC81127A19F4664AF570E43631628876CDBA595103A7FAD5BC02A139A981E0FC14819AD97794432B354D94996A23ACB2CBBD3008A6B852003D22039F09B8FDDB60D427FA655DCD5E95CA26D3F99B538662A40320CA1E4A264F75AAE24B3DA2946EDC1782C6133CEEB458E14049410B8F094BC50C70BA3FC6591F3E9E02294C5B383BE6A55FCB0964F08B98DFC25C73799ECFD0D52EFD15C96040D659F73FBE9F4A8AD34DFAF3C1705F1DCA6DE016CA4A124C09CCB8A48623354CAA7C7C9AFC5E4BE64A5D1CBFCF07FEDA0CF68C859E2427EDB5B2F89FD79F24C470425C8F828EA6458CBF747B7562E97223B2B2C1FE9274AE04AEEFEFB6B9676E83FF646A1DAC29C61BAE6B17274A74DCABBEB4F46AAC46D0D59B4063752DCE4531DF3991BD51F378FA87F1BC0D8F60656A928DE314FCDB764FE306503FCF2BC45A8DE0AE36852B5DFBDF63D60E7397148939FD667DEF1D11D4485D0DE5DC15423BC4A62B48C292E92F65378BF39DF1EEBD6A3BB3058472C2F817CB29EEB3F7A2EFEFC3FF2D7A688E52894C5F7365F758E4122B1380F9C047E028896682378A1853E343F055566996CB5D6C60EADB5C92C7D480467437DD5620DCB7A7AA40331BCEA497260414E5EB6E174FE606D3BA8977E94D46DA4A779359A7A0F1761DC7FE471CBCB6506BD4A5E6B3C9B5CCA33979568574FB49E2A9CB8197A4E206E5CD55CFCA35AE6363B9A1EFC6F0F8C41D1080C2C7BC90F881582C51A7DEBBC418D9E337C27E72EB838608E281291E4EF69C7EDB305C896D98DF94AEC31AB35276CFB4A3D5E3A11878E51A3645C592D26988A6CA14A8438A84FC022E38E34FA15B2653EF129813B1777AB7236B6F5B603405C0C56ED91EB34921B15DA78AAF024D1A726C1539B4675AF8260D12C1CD211C06EC2FE76A6F6E6BE636D211442E3FF8775B07E63394A05E44E82E28AABF695E691725426E4F57C3AD7F102571DFEB169B1ECB64B9DBD14AF2422FA7369409A4D37E1D824DC18ADD3A67B086CA7DC05BC6F1DBF507D1B6DAE06582173315949523C56843E93F3662B0496BA8AA1C1917AD50589A1F766C72E4446BF8FB72AF5B8DD92EF721616B942B893C6DDA9C4C8B637572696DCDD81BB042A8F9CD2ACAC557FD4A450B74F84F54530B63A110694CC13AF1A248EA501304CCBAD1BF299EE112C8F0ED5CA8EAE2648B13689FC3223612C8D608D5B34740A25CAE922DE1CE5A6A0331B59E9B07E2AE35D05DA3DE49EE6217A08439859E6E33141E6BEEE308A39C668D0613CA8CD402721F039DC24A66389F3CB5E9380EBCE93DAEF0FAA6EFBD918CD3DF53090E4AFFD6B7EB3BDA93232C697BA6914D26EEB2B64B5C8939CE94FCAF5323244205DD5FDD2352142810274B9D31BE9FA99FF27DBD6502A4AE5F1D05BB8175570702C16D0DC3AF7F2CAE6BA85E09FC500AD177AD4B1BB53A8CF0CD3A6B5F5397CC445246A20F2D042E2CF5A8095AB988DC3100FFBED813248831560C6CAE7C6A067714DC3AC53D57DA86F68F09DB0E789285D98CC386860F07D80CB91F253265E64C4784E46E2B74CA1C8C6A896936AC1DA446756B502F2E6B9CF2178D339066C9BE085EE8C7EB9A92AA9393000F455F163C270E8E756183079874C5B61D1965E5087B7E527764DAF84353CA9CF263BD248F9603FFDCE2C0D95A007D525993BD1D9944D69EC4172836DC41A9D65CD7C2E24BAEA04B04AEE61889995FBACB55E240E2D29577766AD592B56B0789BDD7FD5B55CAC161D167653EE425818A5F795E62DA6125BAB6156777713BAB9773ADD664D74ECA8942FB37C99F85F0B24573C35E4390C00B56BB63F58D01EBD94BE76D8EE8848805126A723424A4F5846B6C9B040F82C82B1A8E24A5634271782A5B5C75A5D4D374395C8F2DBA59E072B6049CD9851FEE8ED4DFA2658D92FC948726C56B257B831A7571371BE1489682FB1DD1B0438D881F7F8EB7B35A22E7125016B1CB289CFE641BEC5663852598D1B9AF693490AC2D38F2E52B15780F2827120EEB0F36F9256A71C5F94E0485EBE9215E33B05F05946D161B579602DF463FE0338EE27F0D800C66C07ECA9A4230C336423B75D125BE0363318B8B433B36D335C8B9C9271F5E10EB40514874ED72DEF03250AF00B20A426E7028E821F536F16979CC0877708DE98FCD06901DCFBC954978D859CAEBA4C45767703B87D26407231540794FE71F782061D7BA81D097D79369E061B8E8373C7D09E3ACF500135D8A61E5B75533015399D7EC74CE8DBD24721BBB1DDC2D8ED198078FDEB842B3E25F792AA8CC44D1D6C8664EAC1CE2370BFD182B97EF270F6FAE61B7FD2F16403F1B124D71679103E66E9E8DB9573DD96443D2FE51678CDE250C9E644AE7A290EA75F57905716439F31B7E13F1FB0BB3E7C35C783067222ECC3B617BD67BCCA6C57F3AB6BC6B061FB32C487D90369A1639F6D50DF26D7C53351A26843AADFE5D87B30C1A948832AA058D2044F1B01DF1338A7C290BCCB42295FA9794C0C20AEF9BA5B707B7E2437F3264166B1AC7EA803B8F34C7DC2C177AEF41301584C0EE8F7B40D68F36256F3F957B65B967A71A7B6982B40BDD7855D11AB391EBEAB0E578D181D57C645BD43965156C2D779882A8C5577B3CB86ADCCED447B2BE74A97F7C64AD8F900D806ED6C6B3DEB749DCF91B6210A5D6ACAB1C48050B4BDF2BECB4308EBF12D07CB59610B24C84B9737E4E8AA76EF9F50C25ED1A9B2DC46F57FBB8216210A5D6ACAB1C48050B4BDF2BECB430E16EC610D14D53B1EA2BEA843E7C68ACA4D1493B494221D5BBAD78C1204C4CEE45A0054CF560CA3DEB3BC51ECDDFE99250D580A80E1FE6D07F2A33445232CA5F7BBCCE735E2E0D8047969A3BFF25128B4AAD70FD27C9B44D99A525F334194718385FE416805AD86832D9E6EA50BE32C3D579085BB7DD98B6493C25E0A289686B0DE2323A1365EDCBB5F7D4BFFE7A7591343AEA7D3ACB208D208DE8D2CACA0E5B3AA036489299EEABD2219A746165B60883AD764072B0645213ED78FCC5DF68E325AE5DD8CDAAF3A8CD0CFB1340AEB238005299251E9AC31D119C4A955A58CE9576245C44B638CD1E5BF58792BF80421C98AE2E701FA218C1A0AFA308499812D7CCB2AC2049DBE4AEFF5326EC53459653E86696C1965551EF9B00EDA3C698A00245541FFBA45CDA334551C5D791BA2A194F374FE2003EEDC5F5F4A2A078CB21F7F32C71ADBCC9A50EE7ACED604E74C815DECE98DDCF70DA73316E23391C4C76135DC301B03018D899514B93787BE604D70B5892CEFE1679B32DAE02D595F5AEF9FF263230C2C83A98A79AC4F74F292A63BC12DCFF0CE8D73244282D7C3582B745DCD241F10C375F9F765D1F41492FCD0116DC1AE686E5736B88F0B6D690B95E53F86EE35EE18D7AD24A0BC517E5119557C6EDCB559D2562BCDEAD98E63D7E406A5397D5951EBB5841659DAA03061DCED256B409F338F7319341513AAAAF611FC0AE9A233345E02C4F682E18E95CD7ECA7AF6282D4D2D9E8A9C36C95FFDFB947D102A139A981E0FC14819AD97794432B35452414759272796CD27CE6492C6B36400E9CB297A50A1A114D244246CC302C0064871352B45558A10DCD3660F0FD8AAB4FF91B3F2D1606E201930B6D2AA3C74608F5DEBFB67E1AA1F60A4EA0913C764660C0FA64B8D1D0B3F39FBCE6E54D97D26F3CB82EE26853CF2355F628A0B0CE9A015562B9805B94941345BEF64CDC69BDA04789FECE538292322FD7C3B9EA13855E602ED45DA7AF96ABCB880D6CDA6A108808A6625F3EFD2D5B7FC2063134F1E85C5633D81C37450E9A91C9403DA58740FD49E596B8DE72123D1DA81BEF10CD5B427B6175ABBE13CA534D7407BCCF3AC3179D262CEE73E7F8C5712EC4285F2B5B04536AB0454465BB3FD3A3570C77ABDF78A4B4B746E9605160073EFAF2F095CEB8600FFDFCCEA5F9CE1B9CBDD96F814AEAD4C74D0B162B200437DEA981561FF849A0C289E3BB640760235EADA017274A0E793A1782363369CFC3D1127B234E2EF0164987FC41BDC6E926EE671DF0F6AF17CB58D91EF453F5EE4CCBA0A6AC13F61B2FFB4AB9C6049E8B904B9FE468DD1484F7848BF729349A4C66442308F007AB66FC8A10E8D8D586BAD4A8B81B6B43AA6CC48E48D0EFC8865F984E33856742FB418B6776AF92A37A354341E0A1C2C62C27A76541799A4F15E90FF1452001EC58E26E14FC018F43F83AE1564557565555CFC49F4AA76F016606A12CFC91D935F165999C68DABA4651C1AF808278997EDE9C0EDA30185F4844573F10F0D9D297520F522657C3B42C784535CADBEAC69C219F7CF1E412A26E24BCD15AF604BA112DD5E22F644244C348F437C87AE31EC481C861CE582B59B3407DFA00CA2F94ED0EC0B5827CD1ACDC03EDB1D86E687CBE69D033EB4C36CCEA2B7CD881EBB0E0AE7402C02EBD00620AAB063213152456600E4A078FB4CED01BC671B851854EE2FB92F3C29798571BA34AD601F962DD450B07F8B4935F06734A6558664EB65BCCD6473D209D788B0181803FFBCF1FC53651C1945167998770E914F9FD6A9E6D20E7AD714A3C66D937275CB08E1B1122DD4A19390AEDB25CB3A4401E7B77D2B66BD9B2F34711FB756DBA8DB7D3365826AF8CDB12C6FFB775EC5E628FC4074B7FA5C5EEB5F566220B40CD32B9A8F360673F9480CF2E76910B2FE47EA7C364BCBD158833E35860C887C279CFC1E350EDBE1373888C9CA0FD1576869C92159FBE97178294A40A4FA4BFE980011DBD0F05BFC30C84091E8B10736F359C041B03850A5F7EDE25EA8725C7F3E3C64FA9A3433DDFA6FE9E266018B997B36B84A0E73F827DB6C08C93E86BE28344777D2AFFD238132AB8835D574CE9A1EF5BCB80BE9F56F7B03A215BB437166A0CD2A967A409256CA75D86E942BE48C1160279BFFE459EC93EE3C7538B3E7AE8A166F1B01DCF63FD89206CAAF56FE454B9D6D5D51ADD64EAEA6EE5FB89C554944DE9FCCC8173DB52E1E2736975CC251248629E98500BEDDC316C8C1DE9290466C1CF069C6C39EB80885AC5E0C8C7759D4FD2E06CFE1A389E2988EE2CD07F6161A232B5D69AAC893AFBD15C8EC5E1AAA3D8407C6D9CFC9DD1027476D89A4CB4F82C7E6005E3B3BE1A651832E6D3F0B47DAB9F8600A74C8E34A0B17EA74162A19F68705904E060A41046FFE711A090D2C92C95EE33024F9635CC61C6BEFD20AD64F5B4B968D3DA3BF9FC1B5DD69AAAE2F89E66E3D9A3C7E76BDCC209BB46716819662BF1C084BCBA0A237B518BA8145B0EE249A0136CEF181E112A9AE621F078613E7E7B8D5CE1E952F9E5F425300E22B9C62D0AAC3A7BDB7D0CE497D158B8802F10ACA99B2DF9691FE3185127B64D4081E11E27299BCF88B8DD3B64A921B0F24A09CC571005B41C76F8BF4F0D13F9C28F0DCDFA7D05309FA8E6B1E419C472EFA785A4E44A3F3719955B0331F7207D174CC9987F487049AF927301802B12667057E260F53CE5EF4F8CC845CACBE86E3AD6573EE36BF2625442A59BC57B0AD82AE28D0E29846F3CD9DE0A1E77EE99491D9E198146FC4F02CC45424803155F536015D698FB092E7AAD38891926E2279139C906DBA59CEE4E741C1B3C1BC5E00FB75233705B63934B7B4DCE5101C6D2E40D14755F356E4AEC3563B944F4D1D741C3747AC185043BA18B102B698E95CADB19226861BE181F3274223F9F23BD175E90DA8FE2193012478AEE0EC6C708CA7E0DA540D6A3D0F6B1B037E07DFF58A0E4564B2F311C0FEC16F2C1849F005D5A2A138D88F4C6D5064AE021A3DE96EBD8DA09D8311630AE0CA6C7D8508F7CAB060F8C7DBB0B36644453A2881CD6A7B5DB7C193B977AA7014A4C331C754D21F205E62923E22ECDAFDF5058942EE34AD9971F6EB0567949E00F23DAD36A531B1546894317D32702B9BECA963457606F59E68926F0CE72B0A9633F16147935C02C449F9F959755DDB45B114C574C90172F513C5093AD3D35CC3A3151E65B9946B8B5288A89E24CB3B02C030844F593B682AD283D6DF8C343B5A98177EDB44E2E56C4A6CCFB929F7B7163285DB7001FAC759DEC5EFB6104BCA8EA50E97ADCD094AC08379F75D39B88F3DDDF578041996E0AC12FC998B1C22D3B00770FD1B8B6F34AC2233E9688A437183101CCBDFDB51EDE666F89004938E1BDD4C4369058FD7B993B90E9C483AA9C42AA3091E1FFDE2A69C8CB96C39ED2586FD26772D8C54DE25BA7290272AAF946A903B078CBB4D14B517F209F9AD7164A0BD31E02612D3D342B879D4B4C03EA196D6F26A059F375108654842E33EA84C3DACBFE75B6C8EE2C7AC69492DE261CAFA8E677C1B27C3DD88C350B0833A9887E4DE9A4CC38F72F2A6E71DF5038E1EBB7ABB6CABD7ED458CA00BD452855C02F517AC999BF94282CF334713DCB7273E6C8F077A35D9212525C6E1BEC78E7103A00182D5EFD0B1292A179527E25F705FA7662E554AA0D684843D87675CB47CA12350F841E10855DCBACFE083AE39BA5E071BEE5174233C1CF8A703A8F2D737C02E3938C6EE1932A312ADCC08CA489F1E6261480EF79DBB8E78DBC50397F01D90D8810FCB3DB2C5AE1C8089B5A1BEDCFAB6E29A23F28B6F2B593B7CFDAEE43329E8F90C2281FC9D38EF1456D1C0601A50844462F4A0F9EAEEBF8989961EC25C8E48946690D2610E2EF0245B766BE00835ED230B86B71000C3A9515E25D95ADCC076F85D0F0783F557F479878328AB8E685B0C6E98A7BBBC97DB6AD9FCC3200C71A4B496EEA6E796DDE701A66206273F8B19D8E689F2A6FF1BB6203837415C038757C65D06A9158D069B08585FB6B6895F38A2B5E8045AE88D55E0E702722E9141BFCB6581F834C369D1F3DD7C4ECE254848D2B507D500C04FA9A644B184782159EC80354FD2FD724F531198CC782A1E34C0C1F5F001EC232F7364979FF1632497FD6C9FE806D024DDA77E92B9D5CD8FF0591E60C939F4F526837DF3E311D67E4CFE1DA8D505116DF401EAFCC86783A4B954823BA9BD089EB45EAC381968781D09EB214955C6F5305AB209E61A2593A31E282F3FE2C04C6FB0D738FB968458D1EA267F99BFBF6B280FA2568DD1D6A2D80C3A3D021F5940004D20A38F1E85C0DCF0B3D289AFC834956E41E50490F41BC41C6E0958447E1C3DD07D6ADA4CB2BF4AC11443F5698588DA27239EB2C01627398C8252403603AA224843928DB26D783347822707BDD5087B9CAD63C84A1E05E74D40D2C1BC324AE951C59A26F55A9A1022C5C76281CE4BF5B96EAB67063F5F1E02B5CE96AEA9033C949CCA87E8701C5E1D4AF8943E9040C0F1380E801DDEB679D356EAA1C9184564AD94E8AACD98E45163F03F2AEED789F7FBC00761415AAE2A331569853778CCBFF134671AF7A8C015628326BD20ECE436C126AFD5A73F9A649E0651B25959CEA30CDAA5B9222940203AD0DD25F9C6FE5FAE5E1AA4EB277678F0B6EE9EAD96195E1A443953664C02DD871A050370FFBB2AC9571744064225391309919913851A3BD1799B8C0333F22BFBDE47B05DA3BE3801B91CAAF95258BD1F743D39E6C6F11B2F363019F224BCFBC967CA677474A25EACFD7540D0759E2072699A657F621F25BBD25A95402CB3951A876FE114947DCAA035D6352349C7AF97194DCFC188847F8794EEAC4475CAAC4BE7921455979775669B51F0489643AAE52D8AC16CAD92B26EC9C0CCFD16FE0D207CC77A68449C971CDBD999FB21448312FA62A08A50545FFB9BE392F79C37CBE2D7F93E33D7AA4B4AA806309396F2F4DDEC53361FC11D874140C34592D6AAF3ED7659C6CC3394D5EF943F5F5A9016C90CA0BBC4B5F51E55BD0A1E2EB0AD44E624002CA7D8CB195A4E78C9FEF90BA83E613E4B1D563376DD12CCF257D0512BA9B7B9AFBADA306D0E34CFF6C16F3F9CB5581946AE746434067AC9D97A37D4CEAAC65FBBBD605B6555678FEAD1845C8A95B458373014CCC0599939CB50D74C4EA0C87B85D0515CC4D55228BB2928A6D59892ECAAA26006FC924F528AC42FD273379927AE472299D3CA24C71D531BA2DF833FBF5C4B404C178819789C430353EE2A2A3FCE8815F5F640650E7BE540D28817156F8C301B0F45C3373FDBC43A615B83987EAE6C722D7D1141E26F67D7C9698F4D8DC7EDD5E1CB128E2027F1DCE2219562E857F44B508AF20347483302FA7CFB086821CF4DA534504FAC4C3FB6B2A8F5342916C08D65B1AC04E30B16F5CDCD5678B339734D1FCD6EE0FBDE39060E08053390BFEC0DFC4E06454954093182538A42A9B098E7ADA41A51175DD8353D117E3B4A969643C186B8AF071266F8B0CEECFAF0AB76A8853D79C216524E4776F5B45A35ADBE1D526B9D93A425360A52AC08B4792A89251F1265801DC2EFD2D4B9B460FB615B6EAC6C0C5E6B5F2CD0C1DF0D20EAE796B8BB25B3DF4A639A95392B80EBA2F3AAD054C74A1884DB8A5347AFE1B9FF33CBC33CE4D20FFCA2BB0C7F4A131C85F0A0A091E26F99CDA40789B9906C890B90D55CC9589FA360AC35F66DB9A6FC8974FA836F30A3EFB935803A7847E324E6F1ECF54161055689160BCF07A0D0F83EF7422533E2FC340F553DF0F968BA5D5D339E70BFCEB41A6A2A17523C6C6F586A7D506442186D839F64D63AF74D93AABC4BAC05B751D683669E47CEC57A873DF7A31D2ED18712A9CBB98B8E35FB739A4F4CF2072E45F8A2885BF278BD0EFA275250B6901328AF2DBE07EB36AF1A17B622438A2A14B84A17E5AF049064EB2EC5791E0221D95B8DEB057E778812D095063036E0EF9392FBF11792ACAC14892F84E5D0990B727EE9EE58DB88B6CBAC22FF24D3F443D6F73792A41F87AB88332C40B61B3A4288FC88D8DCCD7FDAA042C62CA74D734294FECDE53EDDF59FAE9A7FE651565802CC957C8CC6D1709AA7C6D236605400AE4BDFE19C5BB9BB38F6B990A9F57F17253E623990905244E373EBD5D70A8106CCB68B3CF0326D92916F5B0F5A6CD00F7F13C060129E9272A4544351A073D307E990F627FD99E164108991AE89C7017190EABF0572747BA2673F7CFF9BACEF0A5E73E66AA66AD9C605375D122499E7A60981DCDB7544AA4ADDBB9D2806A35CDE65FC44DE0ED40576CA79023B6E4D996DA6A4823BC2EB933FB27A7C239E00B742209A7056E7982668D06F19AE0ABEFD44FC3EC15A4DF7BCD4F5BEA60802DADA0E8F72EEE72C005603749F3A9A3A675E7660C21640D647FB7A2F68E0280A43C2F4E16C0B62C1335B6DD9CD22A431215E7F1736C3FA887C3023969234B05F57749D721CA5E06A4022033D3A93B071287743AE370D560B3B05FC92B7328BAF1C81D72351CA05EF5EF230CE59D3FF7EAC2124E9720E78F4C286B592DB1B4536BF502C77B8F868582A2D059B8C4E5499480DC74B1581AF3C847D99CAABC7A277963A5397F0F0E3E1A5161134ADAF6BFF2719641394DAAA656478FDAC8337BBBB5F694C9650170D26F200537127A26CE5AA66E525BDB774CA62088FCC42FD2D700DF13440FD1F05BAE3FFBA5098F1B7CC28120639569AD1B4DE726BEF0EA6453EC3930F87A612EAECCF9BEDEAB231920D62916A22489A924E52A18BC66BEF7F125D4BA54F064F4C1F1090A0A1F3DA56BF843815B59DF8C8F6ED4CA3A2226F3DADFDAF64DD01189A98BA951D6EAAFE009694A51A86EDE9BBEFFC2F3157EA077BF3148C669E0D2BE4AD439629B469941ECEE71AE5686A850963FACD34B594029B8DE939D97E73AF9CB396D0CE8A5DEE1E142876B9ED482B8487CAC7AD5367510DD4F531C6C4E1418A1B1031EBED7E3A356771608C6A91DB9AFBBA55997B1959B815B344B465CA4C1D2ADE976F3AC6B764F4BAE21DCBED51D2F5BD16AE309FF92996399FB1E50BC2AE521D6EC2ACF1F93EFCD511A66CCFB2B0C8B9445FF5FD8E774E409C970610379AC3C22443EF82EF87758759A506796179D72F227911DD942E9C00AD8C37C7DC7811A545373EF103059C72A9D1A3D6CF3FFC6C64C77CE6EA687FD14AA8BA6B052E3FC07FB8C39BF7045076B5993487E9F4D25E2D1FD82D0A51FE7895E4D0DCC27698ED42F6BC56F6AECD2A3979A141275EDEE8CC02D2F829F3AB3F4F6C71B9BAC2DAF401996E613293BEB634DC4ED8F206D4F23ECC67954DB3BFBE041158C0B5FF055E51FC087150632FA40B18CDC41B715502CD0071FC98E660532C44340528B7D8252FAEF8B333EB8B46998F5DB67EF7A93AE400F3210F4FD6C3E3220CB35F9703AAB17847223A9BD8BAD1FB9BD049494100557F5EC90CCFA99A95C695BA9DC2888FD9F3DD1F3FA9908E699F02F8E34149E22963B8235B60ECFDF86DC7A03D9AFBDD9A7B55BE142AA9B54D68CCDF647AA441B115E70218C32006CE55AD91A2915D33317E8134F49FEA1DD6F4EFA0110B4862A7C18425AD3EB2ECEBCED908E4F13F9AEE7C9AD53300E0F01542BF7B7E5654D133C43CADE22DCF0257DF572E89B5F953325EE58646D8E2FB5A928400F767FDD794484E0A3483ED34EC6D9CF835F898910527A0003BD3D7699CD22F61E4479737247CBF1D91EEB0ADB38C7E6595C10045437090A3B20D3AD3A36BDB9AF75CCF6CD6453CD7665645D176C3895628C269DEEA0C18B0F0A78A5AD2CD1935835A62A9D53E49F00E31B4B9ADC7F7A48148657C441CF49E0CA57FAB3B6F4508CEC57B3BC42A3F04B076020D40D896BF3E0604237E4A7E661B7BDADA2E7B55DB294E88810C369739D3AE502405B5BF819315FA69B7B1FCE5A1C2C292BFF64639D0CA2D517D91DBB0F3F8A92D89AA3A315EE2BAF5E26DDE82AA332142AB0D5919DC75880DCA8F69E113DDABC635165C933285F118C7CBB8C1A9F71D7E2ADCC5CE52604731CBD6E6ABE886AF1F38E3EC54E21CA4E5F2D73D39DC9A76F1F8A2529AFC20088EFB3AD3DBB2CC00F609B56D4BA2D4C692D6320940B172A946C02329820FB99CD61AD1C938C96E1E88E8C7885D288B5D35BC0701E1FEDECC61808D486C90ABF38109EE0D3133D4EC2B50F0762BE8778B8D9CB9E8BC93AA16131B5722AC9E2E8CC92E1B461301EE73CAB9290F5564D6CA567A15FE408EB63330E6E71C68139F34F9BEE5AA43C09D4586E8ED13D88584225CFFB3DC636BAD330A14F628D406F16B15D8B34C4B66570684F1186C4D77EFE0A1B15B7723B2DEE59722F8239469B3C7AEA53FDFF2ADDF7348349795F5BBE2485FD1466D5ADE2265D6AF775DD03481B4B8D645DE335D3562E93A93CFEF1F1DA7E6B964EC6382912F1FEEE36053582960B13CF36BC20CBC31BB4406E7DED085006FED5D91B7AB2A684A0939325302B10652E4A4F29D19B629E65DC0B495970419237FABEBBF313CF0D3F90F1DCE2B6BE18DF96F362ACD78B23A156529891E0526BC351566FB135F3461AC0C46C91B23331C8FAFC886A5DFC27EBF8B8D8BE733398B894A1CA4F2C1B09610E1EAB2B5CFF674AFF27A80D63F4329D79A18579E2B873C300DB9CF226BB89925591B79DF9C2CF87886FD7898C3D545C6DDF38B8628D565E08CCF92040D0A261A430818C68DE1F1D65296D8AEE79BF7A3C00DDE2DD1215BF2F03E732FA4E92B9721A2CA29B284EFE07EDEE8462EFD997403A1D4DB6A6672199AFB870DB2BA46958055C508FFBF13C77A8FF93A88F18C78E692771EC7F3AAFBC9C9720ECD883ED3EB4DEC66BFBE3B83EE27DC9EA3DB6AD83030B826A1C74C67450B7726BD358EDB53B97461AC81ED299296668731E73E76BA3F65ECB46F23E57B4897027A2CBC030FC3546FD950DE96C75CF1F052EBB36A8EEBF48119E87C5995AD0E84A6815B7C903B2FAB897475478499ED821C972AA305EB3778D957AB11600D331C90682C3D07669622B05492940DCB194844E155603D45A0A791FB2C9ECAC176DE2B78BDC8C838C113E338D673848CBF55E8781829957487643275AF62C9E6B630B9B21CAFA507361D8BD39D0F5E32BE3B0D2EB820F4D06B67A1819DDF3142912E48F685B75583755982FDB530BCF29A1E2EE58CAB1594FA5E931CDC52B4FBD94F55972B44D123D6E26AAB1F60AC9167D579EB3810619765E13157BFA39AC2AC0BE772CA0EDD82F561CE893167ED4A4E61808F878B8CE364D36E4B3D1538B7DEDED012787609161EFD16AE65B389B39552DD1ACD3BC55904DB90FBCC807BA468A9A7941F861A317D958D089DE00BF376711295D4C96B6964C6870AA2B5FB78F726693E5220AA311C7AD24BF1D0FC50C7EEBA0B286CBAA1D3511326D76E4934322E6ED26F268E03E4306AB8D670FBD1D304B64209DE3F267E2996B732C07032E6787E5636F9409E9502C5B1B32C267D5997AE87AD6903F38BE9ECB0EC6D0F2E495502BBDBB9A4CBE12DAB09356D3C69EB074FB648AD1C54C277FDE4069D43C6ABBE5EA5E6AD483C16B6C3222EB499E053C77206F9C220C51338E31EE28A98A219FC1DF57DB399763883E6895DFC3C7A832DAC4A43F6D531ACE173740520C6A42936B21234948B31FBBD5A58F1B8ADAF8540F0E428C73676D4E74DD131D8A8B2369129AB146E364375290A1686E2A4A865C5D24ABF6658FEB50F18C3E45C0139D35AAFC523D95E0E06FC06613A7A764347318E1552E7C3868269B7C1697ED0148376F1F7B070BAC5B0E9ACC3A90EEB234124EBBF1D19C73447F7DA0A302BCEF9604281ABFDBBDE857683FD4B6EF613DF24154D34FA40F25837B471C3EB4EAF6DEFF1CBCB765F11D846BB684E816D93B2ECD85ACB3E9D1333EA84D8D224E88A78F6E67468341B7B0F23AD5C80FC7C72157EE732A4398AB95F395A63D5015787552F548A11DFA9BE245F1EE813CC7850A09EE035203FFA3366F0BC70F00367C85C695290A134BE6BA1EABBDCD1C9B4E0C14177BB0541DE87033699B0DEDFE5523CB9DDB331AEEA5F6BCF4BFACD9866D569AE43BC4C2D18C685C4FD132ED5920959279EC65ABE2B23B68FF349912D568EC3CC4AF6EDA4FC3561C9185BF40156B22E5A0CF195C8FC891C43F560BEC196FD760FF8964D332CC6572E91AFA9498E78E8ED43E4248B7EBA31952F7E7799D647A1BACBF106E0DFEAE953189DF61B1C940BB65AE1B70D1D9A510BD8432E9AE6025D3F531B6AA2EA77CBC49186BAC7237A288FEBF02304EF0B2EBF6273AAC3895802FB0E614B5C9E73167ABC2E833CCFE885E4129663F23E9A7ECF3D62F2583BCE67304812DA64234137BF67C4678B4E7FC10D5BFBF478BA29C1729E64231ABE0F611BFD9D40B48B99C37678073BA402885ED58A9824663F8DCE9BFAA709C473CFA28136926CC395F675EA2F34E9EFEF527279DC72D1964AC1B1E45C7F0256AC6C7FB83BE783CDC4317C8C7BA70E02F072969C3AA6CBC0346DA6C3B100E3BF60D52A4AEABC809D9BF55B74A51DC018115790BFAA5AB9FDD5BBB7CB0BF3C47E9A4A9AECD21ECB8F0EBD5CE2C06F262EF2F082DEE76B1253010BE670FA67AFF89059088AC776D30C2A03BCC612A6AFE0CC9003F435A34987D36D1F9DFA105167918BDC0B12D007DD96B99156F518ECF92D99795B5E0CA7D58E977CE35F87E8F6EA57663F9638A32E8D94EFB242DDF5EA767BE5EFAE3C2C7FCE1F16C55060A7D3DA23212CE709BC337EBE6C4DEABF60F7CF6E3AAC49CC561BF1759136009A454A2A0E5E665194E0063B6E5FD3983A45CDFB10D7F8119853804A4C3740F578E83BE95329FD1778B169BC94DAF1FD10D5417FE70011D4329B615E34C72F32A4C85AFF4E90028444EC41DDC9FE1E3C7964E1307A9457FE4A14DEFFCD14324C11BB3592529367C0CAA7AFA9874B0BA51140040316EFA4FFE8422B38EF5943D5E96FBA920CA7BB76BF7278DD9E9E95378E77F9AC079095032FF70414B97FB3D795B2B769CDCF1995AFFBEA094F2621453985F4DA2EC2D4F1E5F14D5D938C5E85B78F8B479943F3D93E66CB464536D2F35560448A7DFBC8D824BB2723B6EF271A681D85BBE0DF3433E93708A17EC56831ABCE174FE260C99E49C01FB11503B6327A8DD2E202429AC480392C0642E3A4ADD2DF6D3FB7D7622A8E23F9D082B6F2816A53E5F03614BE7665C093C9F9391563ACF934DAAC84F1C6C908CA83DB9D67C349869AFB3B6AE5384AA3E04E522874FA18D13266563482F795570FFD1443EFFE14AF86391442186FB7D4AA45EC5F9569C3D6EE38B35BF95E7354E8E5F40D62E35E815714F8730EFCBD9B064437B96E4322AB96570E225E45309A237B57BA7EDD37E73AB49FDA7820AC10DDFF2DE47BCD7960F64E9B2403BE63D50347C8D89A66A090C05DCFE7892F87D3984B64906934ADF36E3DDC3B664B350E2AF9157584F09B0506901226F2C566B85342F4BD1AE96446F40AE873BE92BC66A0643DD8C9286ACC8655B4C767377098B2EFA2D11382C0FD6CD83E57C7204F5A2BE5C7172DA69EBE79033DD7A30448C28B4AE78449A041FDC9F3381BA5E7A2FBD48F6743F487BA59C55F4FEE2504AE6D0DFA241801DFE79EAF470EFFDD4795E75F061DB6C53F97CCE537BE0ACDE2DBB4858C2F62A4313251D7502C145CEBC430BABC8922ECD5D1C2B78CD08820C8AA619FE98A1B15228323A392F523F1C7C448CF025CA57B58B532F6ABA0948AE3BE921CB0985C8A0394F6D8998E271F7E3BE9E707F4F43219CF3527008B126F4DB85983E83414919FB1882FAC4516CC276CF5F67D6E048170726F78BBCE782F6162043573FA8F3E3D6177173C9FD9052441503629C4D4EFCE68F6E3E967CC19D92923206E49590C20E4E5A38A4BF71992953447E27FD8FB4928E09FB3A9E04568162B0ACC8BCBE37B201CDB699E9E1F2269283B3B93FE2BAF621E4CC434C1DFA6EBCB58268958344D684CD40316EFA4FFE8422B38EF5943D5E96FBA920CA7BB76BF7278DD9E9E95378E77F9AC079095032FF70414B97FB3D795B2BBF811C9A99ACB8B32A9F904AEC602FF1E7FE4DDFB85F737F3E6BAC095E6E30D30FFB58CEE8C00A2E20B4BA4A1C6C057C6C634A675B9A542D5FDB862F92AF0F2A3DB5E63E6A747990B39898F4EBDC9DE7C649C45E1D0FC0098EC64726E929CE620A875B4B98539CBF6EFCA8EF192CB90FB65F9196AAC1D8A99CE268EA960B211D9A694B308C4EB02680C19212769CEB109E71628D4F9442FD076A1AED785263DA0885D0C7C1EA93D2C1475C402352F1317088507E0E4216F79CBE4A83E02456287D1400208A9EE8E1472508DC2715E028508FDDCAABE0563F3C036A75B42969F37C0C3D4C29F5553AEBA773F20D5112EA447E390667294F39EB328155010513111C044FB066263AFA6107F3C069F981A95C8DAC20A4E8E40E86B3135DDA286BF6EDE2392A7E71FD027122E2BC3EC5BCFAB833DEB56E77FBAD9F738F7032C6921C51657260C2CF05F3D1358587FC56EAAFA63ED0D2394D2B69FBC6C5B0F34DA7FA6092CC5C318705050C7EB7ED9E158E9599C53B36500027FEDAF2A241C530F7866DB36A0BDFFAEB74504EB0363FC783DE998103C3421172B4C40CC7B01607EB22E47C24D13A0A3BCF0739E38F359813808EF71255E01B90A0AFC619464BEACD0AA8969A2248B2D738B059FBA4643784E67221E6D0C6142A83048D7366E5D3D180ADE9E95A79EDFFDB2B892AB7B3A2F25DE0550EA4B52E9B1C5E2C4F65480B17797F28493C38FD23F3EEA21162E8297B86E49B5283477759DB53E9AD4FD869E7507C60876942968DAA8DF9B7808EC10F7F9C296F862038F8A1E77FF5E3D3A9309DDCF6BBA0660D19B292851FF123712D1E51E9D2F7616A8098D9DF00B0BCEBDD1E15BBFD68CB3C92707E190F7881BB9FCF928F8536F28C7804A00018AFA9BD1937F38BDD65B176BAB441D727C4D3071BDEFC170A84C615F8F53B0D42763519DDB491FF871AE4AC985C9647861FE883E2A0C5B24D02A52A639F8881E095FAF9067F57D4094BE5F2E0F191F7488CD3DD93CA1C5082B6956E6F62DE3061840D3FD8FC3CE5DBEC65E3989AE612D6796FF907F1C6A1891DE2235BB073CDDE9E47588FD4DCF6BBA0660D19B292851FF123712D1E771DF77A062186EDB5F7F030303B19D015BBFD68CB3C92707E190F7881BB9FCF26B2AEC7C1B95C2D2216CC6C134619176DC89CE3D25790BD0BE8E612220D607D52FD79CA26DAB4ECED9EBA35C3126E098FF81ACE1366037AB8D0C81FC87C6AF0C23627B5967B8CBBD27CC6C1A62EEF6DDBAF2F0CCB2CA2609AD9EF3C6EFAFD5416BD7EB16DCE8FC6E2FFBCB0CE080343C8F3E69A6C2C1E7C8FA67429E3B47A24E6CFC8476676A0EE5B9CD485A7752FB9653574640293B018BBEE15D1830C17D52E8401B67BE62F0B075BDDE27A922B45352ADA1D0A0292657CFF5B4E6BD6DAC617813C38D4A65A12C58CDD9FCC6A3953AD27274429EB34EDD58DCB0099E67F1B742EB7053B262155F1FD47E92B089A359E76C4D285AEFD0AF5B3CCD41BF7BA5DFD5CA9E29B212DFCFC02ECFBAA4775EBD5E697934CF243CD211E611FF9C54E2B43A21C8DDAA35E923E92F32CFC0F44A168E26E308F0D5C660DF601CFB6D9A35BBDCC16F5CA67ADF167F35313834A4CC8F7D2FA689C01C71D049E4856B1F10E79B35EBE3139CBED5AB4D1E685596BC92DCABF8DCD54852139AD03C811DF81F8176B417154C47F87EB13B5C783B0A1BEA70C6394F28DBC1C934F098FE167E1DCC7B695746C15E47E9DF2F390EB7ACBACE327EA0DC3E20A2D4E7F657D09E5D3CFAE463CC7CDDF7E38A978D5D5AF35B98C910DC2C79EB3A53B750C417C6C14AE6C16FD611B2A9A8BB91DF3418CE7EB7BA87F8B2C6FD26D1019E39DDF5DA802837457A98C9DEE2B6FB90AF4068DECA8F5E8B2AAF48645760584C602AD3BFAB4BD8369EB0D958EBC89C0FF2C4A73FE1CCAA802370863CD24ECB0A0A4933F2F02B5A707B5FBA9A778391055FC70CE6753DF36C2D62D49C91F62DF6E42DDA7303387E463C2C382A8A1B430A6262BD6C9D9838DA67F3631E7A8EB41E360F0CA190DA43C3AF195727B41AC51DAB4B8E2D6C58D242BF388B7738FC09194DCDDAF1F859558FD973721FB9F6FBFF748650CE4F2D12BCBE241C5AAFD717A20B80F6C5E53D3A5F50B8463A57AC39657B13D410BF975D5D65494648AB8826448E3053038D475CE42F72F27BEF2E36033F12F96AE8F719B1F31CE6AA346AFA23537D743DF5C430E96E8CD1F6CEB50E75174FA04A7AE8948EB89D46FC9BA233BA4AD8B9C57E7B357B06A7ACD29C8AB7D702F7BF18B990FECD15A3BB2AFA7B415EB09A3F48C341BBF7EEDE24B339C8515010E0C4CCAFCFBB2813EFA9A84FC466991FAE52715EC4C26A7E0E0063917E5BCFD96AE966AF846F25EE04CD6174E649CA7B8A52B803D3B4C26A14D4AA544CBE1D12020FF078947879C1B6D553545CEF2BF28C1807EF7216BB6DB3388F97A8DE80A5B921AFAA5D0EF8462A9F6B064E1186D3AC5EB012C28C10FAD0B58CFCAED362E6B9B6C47A7993602876E780F4A08665F65DD3A4C6B7BD24EF38EE86430A80820338ABA932A22AAEFE25D7BE2E2DC0C7D1ABF5055C7F52B63A2D9FAB474CC49C5F886890A9E90C094505635EE7E4F65C1C14B0410D1A14527679F0C88C11ED4EB528C3C7930B1D093F43CE6E5562A14F5CF151174AE14B98E90D2A8115BDA20174856B71406C627492190E8F8CB8346D5796C0BE39A19396A62521F19A76A36C0D7A0816AD5ED717B33EB742904C1E980BD425B3C936B3AAC9BF0FEC84369C8EB3ABF1072FF63AF5E6E8CADD00F0336624AFB714ACD524C0594F3080ABBE44F197BC6AC77542D4FF7EB10092227615640A3C03258794D96936AAA34CF32394EF04E29EDCCE2186081421BF595FAD4E7BC9725F4E9789C09E067C8C6F2F0C80F13FE43764CC3EE9A0D813F4107CE388CD20CB76298693F6D4D6CE65BBA32DF9B80F6FD32B8D53975A2B20C96151C63EEFE7EE3458E32942DE4FC775C3AE4BCF7AF90BD6028E5AA9BDAE803DB6C023589F664903EB889A8EF1BF27EEF271DAEEA0E398F68AF5078A9D33162503D240DBD16A8A8A7EE078D612857938A53E91DED56EBD9DC0E757F88FB48DEA48FF701406004461A9AB5125F189D882349F70774ACCB4D45D9DDD20329E4D856A77A5E644D73AA92585D2E286F41227B95AD050278E206477617DCE3DABBD72B81621D2BF3B8B44BC6FC55C99DBC610FD4630B9E868455ED5E69CCF18F48E90C6B2E613A06651769D0BB31A879A38C12A85138834043C6545A85A8E8317ACC7EBCC0BE492D7D6028E1BFD2BDBD1D515142FFA9B9BE42507DDF8CB202A2B9B6E8E13A61D11A13ED54326F73CE6A28BF1803B4E9758B07D19D9D30F3BFDEEEB79DD51B4D2C7A4D3C4187E8D402AF3B5F103411F992722B213362FA7A07619FC7A296EEA585382D1082C52E5C4EC703F3997DDB14745DAB7CBD9EEEFB4D0CE5F01FABDDFC31A9B0F0D7A8B2C791807440786E6D7E9A94EE6D67FCC70F40DCCBFEFB12951986159254BB422F86A54A5889BF871043222858E51CA4E41A8EC4487F5D41077DCB856E73B9410075D761E6613074AF1E00A9DFA7DB9146DB9C4833730800976C3AF55DCC50E70D52FDBDF082BE30BDA4AFF91F4FEF12C926BBA928B4F44E7206F2A604A80D25F49AA6D231FB0694AEF6DB4ACC8A6CC9B969581431CCE458B660F205CA117682050494324AE3EB758049F8EECF4A3073FB8FFEDF54C2420ADA9520BDC946112A64908D4A072394371341A42DFED1CCC2BB2BF55AD6138325F46FE8B8B61ABA0A7AB2679E3BBEE7CEB18E93FD5AD0710AD9B63548F268DE94E7A32D5BAF57F8E9AAD87017AB398FAF8CFEF856C4F376DB77B30F1E79E7E4F1D08DC72F0ADC6B8D79F7E1A9F262226758EBFF31E1011E02A1D3374B2C85DFD6D28A28984A84CAB3B36C07D589D48A1E59E0C0D4AE81B58EFD6C13D741A10F91D2FF77F33ADCE2653D96CE25A95C8340C1766D78E4A951C833D0F1F1C0C037248262EB18F4942D8F0EA40AB94A409C0854B3F8C078EAFA7E7DD6C2F4539B9076B150FBB4D8DE7D6E6D8AF0EA0AC821B1BFA57677FD1A2DB02AC05A1B306459D775DD67DA3EE957C3740B6F777D198B88A27CE86CB795FD742350A7F39F5FE058B3A382D1B8FE121EF236415EFFDEB739B19A6EF285262C1F0C845C9A7B1FADABA3E2E87AE3175A1C610FFDCAA148B32D726D741F7DBB8AC0D0E4D8BCDA02F6F44F1A2171999237D22EBD42977D2C4CF4E1C8069E7E7056B7A9157AEF4E0A3AA6B2673054ED50D7ABAE5D0052D08DEA4E7BC834DC440F3E5F2939DE72BEA396D9F4EDF8D588208271EF89199E28A74BEFBCEA0C66825FD6AD76A88D302E6C9716EA883101DC8CFE5AACE738A6C1F33B633C5E1869F7CCD3A26218965E407F661E3B589A2633F83EF46BCFA25785252710C7877DD8B93C9D0A28F1468AD3AF67C98FD20820AEFDF1842CA880390F22C7389D6676D77EF34203F024C4DFB539320869FD1B6214A697C5E9378B7E63B6F38A7D55CD9F5B5139A7B79AB6F3435DC2D7B432C3C325D604019E22CC16FCC52AE6E5C5784212309D5935C1DBC11B2BB22AAF7855B87131A2BD4FEDD6C921474CC89CDFCE7A8C91A8D49E7F892A78223E796A39C5132093A66BCD222E74B48CAD76A4A00551B6C5E1B3E9BDA986ABE914CDE65ED34389D4F601F73137A91F37EAFD91DA4F8BFD398ECFD423A930058CAF529D3BC7320EB6B6F7E18CF15C44986BA1CFDEE84049BAB3C1BC74851BD \ No newline at end of file diff --git "a/spider/catvod/\351\205\267\351\205\267[\345\256\230].js" "b/spider/catvod/\351\205\267\351\205\267[\345\256\230].js" new file mode 100644 index 00000000..754e204f --- /dev/null +++ "b/spider/catvod/\351\205\267\351\205\267[\345\256\230].js" @@ -0,0 +1,356 @@ +/** + * 优酷视频 - 猫影视/TVBox JS爬虫格式 + * 调用壳子超级解析功能(壳子会自动读取json配置) + @header({ + searchable: 1, + filterable: 1, + quickSearch: 1, + title: '酷酷[官]', + lang: 'cat' + }) + */ + +class Spider extends BaseSpider { + + constructor() { + super(); + this.host = 'https://www.youku.com'; + this.sessionStore = {}; + + this.headers = { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', + 'Referer': 'https://www.youku.com', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8', + 'Accept-Encoding': 'gzip, deflate, br', + 'Connection': 'keep-alive' + }; + } + + init(extend = '') { + return ''; + } + + getName() { + return '优酷视频'; + } + + isVideoFormat(url) { + return true; + } + + manualVideoCheck() { + return false; + } + + destroy() { + // 清理资源 + } + + homeContent(filter) { + const categories = '电视剧&电影&综艺&动漫&少儿&纪录片&文化&亲子&教育&搞笑&生活&体育&音乐&游戏'.split('&'); + + const result = { + class: categories.map(name => ({ + type_id: name, + type_name: name + })) + }; + + return result; + } + + homeVideoContent() { + return {list: []}; + } + + async categoryContent(tid, pg, filter, extend) { + try { + const page = parseInt(pg) || 1; + let filterObj = {}; + + if (extend && typeof extend === 'object') { + filterObj = extend; + } + + filterObj.type = tid; + const paramsStr = JSON.stringify(filterObj); + + let url = `https://www.youku.com/category/data?optionRefresh=1&pageNo=${page}¶ms=${encodeURIComponent(paramsStr)}`; + + // 处理session + if (page > 1 && this.sessionStore[tid]) { + url = url.replace("optionRefresh=1", `session=${encodeURIComponent(this.sessionStore[tid])}`); + } + + const response = await this.fetch(url, {}, this.headers); + const resData = response.data; + + if (resData.data && resData.data.filterData && resData.data.filterData.session) { + this.sessionStore[tid] = JSON.stringify(resData.data.filterData.session); + } + + const videos = []; + if (resData.data && resData.data.filterData && Array.isArray(resData.data.filterData.listData)) { + const lists = resData.data.filterData.listData; + for (const it of lists) { + let vid = ""; + if (it.videoLink && it.videoLink.includes("id_")) { + vid = it.videoLink.split("id_")[1].split(".html")[0]; + } else { + vid = "msearch:" + it.title; + } + + videos.push({ + vod_id: vid, + vod_name: it.title || '', + vod_pic: it.img || '', + vod_remarks: it.summary || '', + vod_content: it.subTitle || '' + }); + } + } + + return { + list: videos, + page: page, + pagecount: 9999, + limit: 20, + total: 999999 + }; + + } catch (error) { + console.error(`categoryContent error: ${error.message}`); + return { + list: [], + page: pg, + pagecount: 0, + limit: 20, + total: 0 + }; + } + } + + safeFixYoukuInitialData(rawStr) { + if (!rawStr) return '{}'; + let s = rawStr + .replace(/^[\s\S]*?window\.__INITIAL_DATA__\s*[=:]\s*/, '') + .replace(/;[\s\S]*$/, '') + .replace(/\.{3,}[\s\S]*$/, '') + .replace(/,\s*$/, '') + .trim(); + + if (!s || s.length < 2 || !/^\{/.test(s)) { + return '{}'; + } + + let open = 0, close = 0; + for (let char of s) { + if (char === '{') open++; + if (char === '}') close++; + } + + if (open > close) { + s += '}'.repeat(open - close); + } + if (!s.startsWith('{')) { + s = '{' + s; + } + if (!s.endsWith('}')) { + s += '}'; + } + + return s; + } + + getSafe(obj, path, defaultValue = '') { + if (!obj || typeof obj !== 'object') return defaultValue; + try { + return path.split('.').reduce((o, key) => { + if (o == null) return defaultValue; + return o[key]; + }, obj) ?? defaultValue; + } catch { + return defaultValue; + } + } + + async detailContent(ids) { + try { + const id = ids[0]; + + // 获取剧集列表 + const apiUrl = `https://search.youku.com/api/search?appScene=show_episode&showIds=${id}`; + const apiResponse = await this.fetch(apiUrl, {}, this.headers); + const jsonData = apiResponse.data; + const videoLists = jsonData.serisesList || []; + + // 构建播放列表 + const playUrls = []; + if (videoLists.length > 0) { + for (const item of videoLists) { + const title = item.showVideoStage?.replace("期", "集") || + item.displayName || + item.title || + `第${item.index || '?'}集`; + const url = `https://v.youku.com/v_show/id_${item.videoId}.html`; + playUrls.push(`${title}$${url}`); + } + } + + // 获取详情信息 + let detailInfo = { + title: '', + cover: '', + category: '', + remarks: '', + desc: '' + }; + + try { + const detailUrl = `https://v.youku.com/v_show/id_${id}.html`; + const htmlResponse = await this.fetch(detailUrl, { + headers: { + ...this.headers, + 'Referer': 'https://v.youku.com/' + } + }); + const html = htmlResponse.data; + + // 检查是否触发人机验证 + if (html.includes("人机验证") || html.includes("captcha") || html.includes("verify")) { + detailInfo.desc = "触发优酷人机验证,建议在浏览器中访问优酷官网解除限制后再重试"; + } else if (html.includes("window.__INITIAL_DATA__ =")) { + let dataStr = html.split("window.__INITIAL_DATA__ =")[1]?.split(";")?.[0]?.trim() || '{}'; + dataStr = this.safeFixYoukuInitialData(dataStr); + + try { + const detailJson = JSON.parse(dataStr); + const item = this.getSafe(detailJson, 'moduleList.0.components.0.itemList.0', {}); + const extra = this.getSafe(detailJson, 'pageMap.extra', {}); + + detailInfo.title = item.introTitle || extra.showName || videoLists[0]?.title || ''; + detailInfo.cover = item.showImgV || extra.showImgV || extra.showImg || ''; + detailInfo.category = item.showGenre || extra.videoCategory || ''; + detailInfo.remarks = item.introSubTitle || extra.showSubtitle || item.mark?.text || ''; + } catch (parseErr) { + console.error(`JSON解析失败: ${parseErr.message}`); + } + } else { + detailInfo.title = videoLists[0]?.title?.split(" ")[0] || ''; + } + } catch (detailError) { + console.error(`获取详情失败: ${detailError.message}`); + } + + const vod = { + vod_id: id, + vod_name: detailInfo.title || videoLists[0]?.title || '未知标题', + type_name: detailInfo.category || '', + vod_year: '', + vod_remarks: detailInfo.remarks || '', + vod_content: detailInfo.desc || (detailInfo.remarks ? `简介: ${detailInfo.remarks}` : '暂无简介'), + vod_play_from: playUrls.length > 0 ? '优酷视频' : '', + vod_play_url: playUrls.length > 0 ? playUrls.join('#') : '' + }; + + return {list: [vod]}; + + } catch (error) { + console.error(`detailContent error: ${error.message}`); + return {list: []}; + } + } + + async searchContent(key, quick, pg = '1') { + try { + const page = parseInt(pg) || 1; + const url = `https://search.youku.com/api/search?pg=${page}&keyword=${encodeURIComponent(key)}`; + + const response = await this.fetch(url, {}, this.headers); + const data = response.data; + + const videos = []; + + if (data && Array.isArray(data.pageComponentList)) { + for (const item of data.pageComponentList) { + if (item.commonData) { + const common = item.commonData; + let vid = common.showId || ''; + + if (!vid && common.titleDTO && common.titleDTO.displayName) { + vid = `msearch:${common.titleDTO.displayName}`; + } + + videos.push({ + vod_id: vid, + vod_name: common.titleDTO?.displayName || '', + vod_pic: common.posterDTO?.vThumbUrl || '', + vod_remarks: common.stripeBottom || '', + vod_content: common.updateNotice || '' + }); + } + } + } + + return { + list: videos, + page: page, + pagecount: 9999, + limit: 20, + total: 999999 + }; + + } catch (error) { + console.error(`searchContent error: ${error.message}`); + return { + list: [], + page: pg, + pagecount: 0, + limit: 20, + total: 0 + }; + } + } + + async playerContent(flag, id, vipFlags) { + try { + // 关键:调用壳子超级解析 + // 壳子会自动读取json配置中的解析规则 + const playData = { + parse: 1, // 必须为1,表示需要解析 + jx: 1, // 必须为1,启用解析 + play_parse: true, // 启用播放解析 + parse_type: '壳子超级解析', + parse_source: '优酷视频', + url: id, // 原始优酷链接 + header: JSON.stringify({ + 'User-Agent': this.headers['User-Agent'], + 'Referer': 'https://www.youku.com', + 'Origin': 'https://www.youku.com' + }) + }; + + return playData; + + } catch (error) { + console.error(`playerContent error: ${error.message}`); + // 即使出错也返回超级解析参数,让壳子处理 + return { + parse: 1, + jx: 1, + play_parse: true, + parse_type: '壳子超级解析', + parse_source: '优酷视频', + url: id, + header: JSON.stringify(this.headers) + }; + } + } + + localProxy(param) { + return null; + } +} + +export default new Spider(); \ No newline at end of file diff --git "a/spider/js/3Q\345\275\261\350\247\206[\344\274\230].js" "b/spider/js/3Q\345\275\261\350\247\206[\344\274\230].js" new file mode 100644 index 00000000..7236e339 --- /dev/null +++ "b/spider/js/3Q\345\275\261\350\247\206[\344\274\230].js" @@ -0,0 +1,13 @@ +/* +@header({ + searchable: 1, + filterable: 1, + quickSearch: 0, + title: '3Q影视', + author: 'EylinSir', + '类型': '影视', + lang: 'ds' +}) +*/ + +dmFyIHJ1bGUgPSB7CiAgICDnsbvlnos6ICflvbHop4YnLAogICAgYXV0aG9yOiAnRXlsaW5TaXInLAogICAgdGl0bGU6ICczUeW9seinhicsCiAgICBkZXNjOiAnM1HlvbHop4bmupAnLAogICAgaG9zdDogJ2h0dHBzOi8vcXFxeXMuY29tJywKICAgIGhvbWVVcmw6ICdodHRwczovL3FxcXlzLmNvbScsCiAgICB1cmw6ICcvYXBpLnBocC9maWx0ZXIvdm9kP3R5cGVfbmFtZT1meWNsYXNzJnBhZ2U9ZnlwYWdlJnNvcnQ9aGl0cycsCiAgICBzZWFyY2hVcmw6ICcvYXBpLnBocC9zZWFyY2gvaW5kZXg/d2Q9KiomcGFnZT1meXBhZ2UmbGltaXQ9MTUnLAogICAgc2VhcmNoYWJsZTogMSwKICAgIHF1aWNrU2VhcmNoOiAwLAogICAgZmlsdGVyYWJsZTogMSwKICAgIHRpbWVvdXQ6IDEwMDAwLAogICAgcGxheV9wYXJzZTogdHJ1ZSwKICAgIGhlYWRlcnM6IHsKICAgICAgICAnVXNlci1BZ2VudCc6ICdNb3ppbGxhLzUuMCAoV2luZG93cyBOVCAxMC4wOyBXaW42NDsgeDY0KSBBcHBsZVdlYktpdC81MzcuMzYgKEtIVE1MLCBsaWtlIEdlY2tvKSBDaHJvbWUvMTQyLjAuMC4wIFNhZmFyaS81MzcuMzYnLAogICAgICAgICdhY2NlcHQtbGFuZ3VhZ2UnOiAnemgtQ04semg7cT0wLjknLAogICAgICAgICdjYWNoZS1jb250cm9sJzogJ25vLWNhY2hlJywKICAgICAgICAncHJhZ21hJzogJ25vLWNhY2hlJywKICAgICAgICAncHJpb3JpdHknOiAndT0xLCBpJywKICAgICAgICAnc2VjLWNoLXVhJzogJyJDaHJvbWl1bSI7dj0iMTQyIiwgIkdvb2dsZSBDaHJvbWUiO3Y9IjE0MiIsICJOb3RfQSBCcmFuZCI7dj0iOTkiJywKICAgICAgICAnc2VjLWNoLXVhLW1vYmlsZSc6ICI/MCIsCiAgICAgICAgJ3NlYy1jaC11YS1wbGF0Zm9ybSc6ICciV2luZG93cyInLAogICAgICAgICdzZWMtZmV0Y2gtZGVzdCc6ICJlbXB0eSIsCiAgICAgICAgJ3NlYy1mZXRjaC1tb2RlJzogImNvcnMiLAogICAgICAgICdzZWMtZmV0Y2gtc2l0ZSc6ICJzYW1lLW9yaWdpbiIKICAgIH0sCiAgICAKCgogICAganNvbjJ2b2RzOiBmdW5jdGlvbiAoYXJyKSB7CiAgICAgICAgbGV0IHZpZGVvcyA9IFtdOwogICAgICAgIGZvciAoY29uc3QgaSBvZiBhcnIpIHsKICAgICAgICAgICAgbGV0IHR5cGVfbmFtZSA9IGkudHlwZV9uYW1lIHx8ICcnOwogICAgICAgICAgICBpZiAoaS52b2RfY2xhc3MpIHsKICAgICAgICAgICAgICAgIHR5cGVfbmFtZSA9IHR5cGVfbmFtZSArICcsJyArIGkudm9kX2NsYXNzOwogICAgICAgICAgICB9CiAgICAgICAgICAgIHZpZGVvcy5wdXNoKHsKICAgICAgICAgICAgICAgIHRpdGxlOiBpLnZvZF9uYW1lLAogICAgICAgICAgICAgICAgdXJsOiBgJHt0aGlzLmhvc3R9L2FwaS5waHAvdm9kL2dldF9kZXRhaWw/dm9kX2lkPSR7aS52b2RfaWR9YCwKICAgICAgICAgICAgICAgIGRlc2M6IGkudm9kX3JlbWFya3MsCiAgICAgICAgICAgICAgICBwaWNfdXJsOiBpLnZvZF9waWMsCiAgICAgICAgICAgICAgICB2b2RfeWVhcjogaS52b2RfeWVhciwKICAgICAgICAgICAgICAgIHR5cGVfbmFtZTogdHlwZV9uYW1lCiAgICAgICAgICAgIH0pOwogICAgICAgIH0KICAgICAgICByZXR1cm4gdmlkZW9zOwogICAgfSwKICAgIAogICAg6aKE5aSE55CGOiBhc3luYyBmdW5jdGlvbiAoKSB7fSwKICAgIAogICAgY2xhc3NfcGFyc2U6IGFzeW5jIGZ1bmN0aW9uICgpIHsKICAgICAgICBsZXQgdXJsID0gYCR7dGhpcy5ob3N0fS9hcGkucGhwL2luZGV4L2hvbWVgOwogICAgICAgIGxldCByZXNwID0gYXdhaXQgX2ZldGNoKHVybCwgeyBoZWFkZXJzOiB0aGlzLmhlYWRlcnMgfSk7CiAgICAgICAgbGV0IGpzb24gPSBKU09OLnBhcnNlKGF3YWl0IHJlc3AudGV4dCgpKTsKICAgICAgICBsZXQgY2F0ZWdvcmllcyA9IGpzb24uZGF0YS5jYXRlZ29yaWVzOwogICAgICAgIGxldCBjbGFzc2VzID0gW107CiAgICAgICAgZm9yIChjb25zdCBpIG9mIGNhdGVnb3JpZXMpIHsKICAgICAgICAgICAgY2xhc3Nlcy5wdXNoKHsKICAgICAgICAgICAgICAgIHR5cGVfaWQ6IGkudHlwZV9uYW1lLAogICAgICAgICAgICAgICAgdHlwZV9uYW1lOiBpLnR5cGVfbmFtZQogICAgICAgICAgICB9KTsKICAgICAgICB9CiAgICAgICAgcmV0dXJuIHsgY2xhc3M6IGNsYXNzZXMsIGZpbHRlcnM6IHt9IH07CiAgICB9LAogICAgCiAgICDmjqjojZA6IGFzeW5jIGZ1bmN0aW9uICgpIHsKICAgICAgICBsZXQgdXJsID0gYCR7dGhpcy5ob3N0fS9hcGkucGhwL2luZGV4L2hvbWVgOwogICAgICAgIGxldCByZXNwID0gYXdhaXQgX2ZldGNoKHVybCwgeyBoZWFkZXJzOiB0aGlzLmhlYWRlcnMgfSk7CiAgICAgICAgbGV0IGpzb24gPSBKU09OLnBhcnNlKGF3YWl0IHJlc3AudGV4dCgpKTsKICAgICAgICBsZXQgY2F0ZWdvcmllcyA9IGpzb24uZGF0YS5jYXRlZ29yaWVzOwogICAgICAgIGxldCB2aWRlb3MgPSBbXTsKICAgICAgICBmb3IgKGNvbnN0IGkgb2YgY2F0ZWdvcmllcykgewogICAgICAgICAgICB2aWRlb3MucHVzaCguLi50aGlzLmpzb24ydm9kcyhpLnZpZGVvcykpOwogICAgICAgIH0KICAgICAgICByZXR1cm4gc2V0UmVzdWx0KHZpZGVvcyk7CiAgICB9LAogICAgCiAgICDkuIDnuqc6IGFzeW5jIGZ1bmN0aW9uICh0aWQsIHBnLCBmaWx0ZXIsIGV4dGVuZCkgewogICAgICAgIGxldCB1cmwgPSBgJHt0aGlzLmhvc3R9L2FwaS5waHAvZmlsdGVyL3ZvZD90eXBlX25hbWU9JHtlbmNvZGVVUklDb21wb25lbnQodGlkKX0mcGFnZT0ke3BnfSZzb3J0PWhpdHNgOwogICAgICAgIGxldCByZXNwID0gYXdhaXQgX2ZldGNoKHVybCwgeyBoZWFkZXJzOiB0aGlzLmhlYWRlcnMgfSk7CiAgICAgICAgbGV0IGpzb24gPSBKU09OLnBhcnNlKGF3YWl0IHJlc3AudGV4dCgpKTsKICAgICAgICBsZXQgdmlkZW9zID0gdGhpcy5qc29uMnZvZHMoanNvbi5kYXRhKTsKICAgICAgICByZXR1cm4gc2V0UmVzdWx0KHZpZGVvcyk7CiAgICB9LAogICAgCiAgICDkuoznuqc6IGFzeW5jIGZ1bmN0aW9uICgpIHsKICAgICAgICBsZXQgdm9kSWQgPSB0aGlzLmlucHV0Lm1hdGNoKC92b2RfaWQ9KFxkKykvKVsxXTsKICAgICAgICBsZXQgdXJsID0gYCR7dGhpcy5ob3N0fS9hcGkucGhwL3ZvZC9nZXRfZGV0YWlsP3ZvZF9pZD0ke3ZvZElkfWA7CiAgICAgICAgbGV0IHJlc3AgPSBhd2FpdCBfZmV0Y2godXJsLCB7IGhlYWRlcnM6IHRoaXMuaGVhZGVycyB9KTsKICAgICAgICBsZXQganNvbiA9IEpTT04ucGFyc2UoYXdhaXQgcmVzcC50ZXh0KCkpOwogICAgICAgIGxldCBkYXRhID0ganNvbi5kYXRhWzBdOwogICAgICAgIGxldCB2b2RwbGF5ZXIgPSBqc29uLnZvZHBsYXllcjsKICAgICAgICBsZXQgc2hvd3MgPSBbXTsKICAgICAgICBsZXQgcGxheV91cmxzID0gW107CiAgICAgICAgbGV0IHJhd19zaG93cyA9IGRhdGEudm9kX3BsYXlfZnJvbS5zcGxpdCgnJCQkJyk7CiAgICAgICAgbGV0IHJhd191cmxzX2xpc3QgPSBkYXRhLnZvZF9wbGF5X3VybC5zcGxpdCgnJCQkJyk7CiAgICAgICAgZm9yIChsZXQgaSA9IDA7IGkgPCByYXdfc2hvd3MubGVuZ3RoOyBpKyspIHsKICAgICAgICAgICAgbGV0IHNob3dfY29kZSA9IHJhd19zaG93c1tpXTsKICAgICAgICAgICAgbGV0IHVybHNfc3RyID0gcmF3X3VybHNfbGlzdFtpXTsKICAgICAgICAgICAgbGV0IG5lZWRfcGFyc2UgPSAwOwogICAgICAgICAgICBsZXQgaXNfc2hvdyA9IDA7CiAgICAgICAgICAgIGxldCBuYW1lID0gc2hvd19jb2RlOwogICAgICAgICAgICBmb3IgKGNvbnN0IHBsYXllciBvZiB2b2RwbGF5ZXIpIHsKICAgICAgICAgICAgICAgIGlmIChwbGF5ZXIuZnJvbSA9PT0gc2hvd19jb2RlKSB7CiAgICAgICAgICAgICAgICAgICAgaXNfc2hvdyA9IDE7CiAgICAgICAgICAgICAgICAgICAgbmVlZF9wYXJzZSA9IHBsYXllci5kZWNvZGVfc3RhdHVzOwogICAgICAgICAgICAgICAgICAgIGlmIChzaG93X2NvZGUudG9Mb3dlckNhc2UoKSAhPT0gcGxheWVyLnNob3cudG9Mb3dlckNhc2UoKSkgewogICAgICAgICAgICAgICAgICAgICAgICBuYW1lID0gYCR7cGxheWVyLnNob3d9ICgke3Nob3dfY29kZX0pYDsKICAgICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICAgICAgYnJlYWs7CiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgIH0KICAgICAgICAgICAgCiAgICAgICAgICAgIGlmIChpc19zaG93ID09PSAxKSB7CiAgICAgICAgICAgICAgICBsZXQgdXJscyA9IFtdOwogICAgICAgICAgICAgICAgbGV0IGl0ZW1zID0gdXJsc19zdHIuc3BsaXQoJyMnKTsKICAgICAgICAgICAgICAgIGZvciAoY29uc3QgaXRlbSBvZiBpdGVtcykgewogICAgICAgICAgICAgICAgICAgIGlmIChpdGVtLmluY2x1ZGVzKCckJykpIHsKICAgICAgICAgICAgICAgICAgICAgICAgbGV0IHBhcnRzID0gaXRlbS5zcGxpdCgnJCcpOwogICAgICAgICAgICAgICAgICAgICAgICBsZXQgZXBpc29kZSA9IHBhcnRzWzBdOwogICAgICAgICAgICAgICAgICAgICAgICBsZXQgbV91cmwgPSBwYXJ0c1sxXTsKICAgICAgICAgICAgICAgICAgICAgICAgdXJscy5wdXNoKGAke2VwaXNvZGV9JCR7c2hvd19jb2RlfUAke25lZWRfcGFyc2V9QCR7bV91cmx9YCk7CiAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgaWYgKHVybHMubGVuZ3RoID4gMCkgewogICAgICAgICAgICAgICAgICAgIHBsYXlfdXJscy5wdXNoKHVybHMuam9pbignIycpKTsKICAgICAgICAgICAgICAgICAgICBzaG93cy5wdXNoKG5hbWUpOwogICAgICAgICAgICAgICAgfQogICAgICAgICAgICB9CiAgICAgICAgfQogICAgICAgIAogICAgICAgIGxldCBWT0QgPSB7CiAgICAgICAgICAgIHZvZF9pZDogZGF0YS52b2RfaWQudG9TdHJpbmcoKSwKICAgICAgICAgICAgdm9kX25hbWU6IGRhdGEudm9kX25hbWUsCiAgICAgICAgICAgIHZvZF9waWM6IGRhdGEudm9kX3BpYywKICAgICAgICAgICAgdm9kX3JlbWFya3M6IGRhdGEudm9kX3JlbWFya3MsCiAgICAgICAgICAgIHZvZF95ZWFyOiBkYXRhLnZvZF95ZWFyLAogICAgICAgICAgICB2b2RfYXJlYTogZGF0YS52b2RfYXJlYSwKICAgICAgICAgICAgdm9kX2FjdG9yOiBkYXRhLnZvZF9hY3RvciwKICAgICAgICAgICAgdm9kX2RpcmVjdG9yOiBkYXRhLnZvZF9kaXJlY3RvciwKICAgICAgICAgICAgdm9kX2NvbnRlbnQ6IGRhdGEudm9kX2NvbnRlbnQsCiAgICAgICAgICAgIHZvZF9wbGF5X2Zyb206IHNob3dzLmpvaW4oJyQkJCcpLAogICAgICAgICAgICB2b2RfcGxheV91cmw6IHBsYXlfdXJscy5qb2luKCckJCQnKSwKICAgICAgICAgICAgdHlwZV9uYW1lOiBkYXRhLnZvZF9jbGFzcwogICAgICAgIH07CiAgICAgICAgcmV0dXJuIFZPRDsKICAgIH0sCiAgICAKICAgIOaQnOe0ojogYXN5bmMgZnVuY3Rpb24gKCkgewogICAgICAgIGxldCB1cmwgPSBgJHt0aGlzLmhvc3R9L2FwaS5waHAvc2VhcmNoL2luZGV4P3dkPSR7ZW5jb2RlVVJJQ29tcG9uZW50KHRoaXMuS0VZKX0mcGFnZT0ke3RoaXMuTVlfUEFHRX0mbGltaXQ9MTVgOwogICAgICAgIGxldCByZXNwID0gYXdhaXQgX2ZldGNoKHVybCwgeyBoZWFkZXJzOiB0aGlzLmhlYWRlcnMgfSk7CiAgICAgICAgbGV0IGpzb24gPSBKU09OLnBhcnNlKGF3YWl0IHJlc3AudGV4dCgpKTsKICAgICAgICBsZXQgdmlkZW9zID0gdGhpcy5qc29uMnZvZHMoanNvbi5kYXRhKTsKICAgICAgICByZXR1cm4gc2V0UmVzdWx0KHZpZGVvcyk7CiAgICB9LAogICAgCiAgICBsYXp5OiBhc3luYyBmdW5jdGlvbiAoKSB7CiAgICAgICAgbGV0IFtwbGF5X2Zyb20sIG5lZWRfcGFyc2UsIHJhd191cmxdID0gdGhpcy5pbnB1dC5zcGxpdCgnQCcpOwogICAgICAgIGxldCBqeCA9IDA7CiAgICAgICAgbGV0IGZpbmFsX3VybCA9ICcnOwogICAgICAgIGlmIChuZWVkX3BhcnNlID09PSAnMScpIHsKICAgICAgICAgICAgbGV0IGF1dGhfdG9rZW4gPSAnJzsKICAgICAgICAgICAgZm9yIChsZXQgaSA9IDA7IGkgPCAyOyBpKyspIHsKICAgICAgICAgICAgICAgIHRyeSB7CiAgICAgICAgICAgICAgICAgICAgbGV0IGFwaVVybCA9IGAke3RoaXMuaG9zdH0vYXBpLnBocC9kZWNvZGUvdXJsLz91cmw9JHtlbmNvZGVVUklDb21wb25lbnQocmF3X3VybCl9JnZvZEZyb209JHtwbGF5X2Zyb219JHthdXRoX3Rva2VufWA7CiAgICAgICAgICAgICAgICAgICAgbGV0IHJlc3AgPSBhd2FpdCBfZmV0Y2goYXBpVXJsLCB7IGhlYWRlcnM6IHRoaXMuaGVhZGVycyB9KTsKICAgICAgICAgICAgICAgIGxldCBqc29uID0gSlNPTi5wYXJzZShhd2FpdCByZXNwLnRleHQoKSk7CiAgICAgICAgICAgICAgICAgICAgaWYgKGpzb24uY29kZSA9PT0gMiAmJiBqc29uLmNoYWxsZW5nZSkgewogICAgICAgICAgICAgICAgICAgICAgICBsZXQgdG9rZW4gPSBldmFsKGpzb24uY2hhbGxlbmdlKTsKICAgICAgICAgICAgICAgICAgICAgICAgYXV0aF90b2tlbiA9IGAmdG9rZW49JHt0b2tlbn1gOwogICAgICAgICAgICAgICAgICAgICAgICBjb250aW51ZTsKICAgICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICAgICAgbGV0IHBsYXlfdXJsID0ganNvbi5kYXRhOwogICAgICAgICAgICAgICAgICAgIGlmIChwbGF5X3VybCAmJiBwbGF5X3VybC5zdGFydHNXaXRoKCdodHRwJykpIHsKICAgICAgICAgICAgICAgICAgICAgICAgZmluYWxfdXJsID0gcGxheV91cmw7CiAgICAgICAgICAgICAgICAgICAgICAgIGJyZWFrOwogICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgIH0gY2F0Y2ggKGUpIHsKICAgICAgICAgICAgICAgICAgICBjb25zb2xlLmVycm9yKGUpOwogICAgICAgICAgICAgICAgfQogICAgICAgICAgICB9CiAgICAgICAgfQogICAgICAgIAogICAgICAgIGlmICghZmluYWxfdXJsKSB7CiAgICAgICAgICAgIGZpbmFsX3VybCA9IHJhd191cmw7CiAgICAgICAgICAgIGlmICgvKD86d3d3XC5pcWl5aXx2XC5xcXx2XC55b3VrdXx3d3dcLm1ndHZ8d3d3XC5iaWxpYmlsaSlcLmNvbS8udGVzdChyYXdfdXJsKSkgewogICAgICAgICAgICAgICAganggPSAxOwogICAgICAgICAgICB9CiAgICAgICAgfQogICAgICAgIHJldHVybiB7CiAgICAgICAgICAgIHBhcnNlOiBqeCwKICAgICAgICAgICAgdXJsOiBmaW5hbF91cmwsCiAgICAgICAgICAgIGhlYWRlcjogeyAnVXNlci1BZ2VudCc6IHRoaXMuaGVhZGVyc1snVXNlci1BZ2VudCddIH0KICAgICAgICB9OwogICAgfQp9Ow== \ No newline at end of file diff --git a/spider/js/_debug.js b/spider/js/_debug.js new file mode 100644 index 00000000..d32b3df1 --- /dev/null +++ b/spider/js/_debug.js @@ -0,0 +1,85 @@ +// _debug.js +// 测试方法: http://localhost:5757/api/_debug?pwd=dzyyds +var rule = { + title: '_debug', + description: '这是描述', + 类型: '测试', + searchUrl: '', + class_parse: async () => { + log(`[${rule.title}] --class_parse--`); + return [ + {type_id: '1', type_name: '电影'}, + {type_id: '2', type_name: '电视剧'}, + {type_id: '3', type_name: '综艺'}, + {type_id: '4', type_name: '动漫'}, + ] + }, + 预处理: async () => { + log(`[${rule.title}] --预处理--`); + rule.title = '_debug'; + }, + 推荐: async () => { + // return '这是推荐:' + rule.title; + let d = []; + let html = '{}'; + html = await request('https://httpbin.org/headers', { + headers: { + 'Accept': '*/*', + 'User-Agent': '' + } + }); + // log(html); + d.push({ + title: 'request结果1-传空UA', + content: html.parseX.headers, + }); + + html = await request('https://httpbin.org/headers', { + headers: { + 'Accept': '*/*', + 'User-Agent': 'RemoveUserAgent', + } + }); + // log(html); + d.push({ + title: 'request结果2-不传UA', + content: html.parseX.headers, + }); + + html = (await req('https://httpbin.org/headers', { + headers: { + 'Accept': '*/*', + 'User-Agent': 'RemoveUserAgent', + } + })).content; + d.push({ + title: 'req结果-不传UA', + content: html.parseX.headers, + }); + + html = (await req('https://conn.origjoy.com/auth/init?appid=d4eeacc6cec3434fbc8c41608a3056a0&mac=0afa691314fd_a12d4a7c9n12&sn=a12d4a7c9n12&time=1768728113&ver=2.0&vn=4.1.3.03281430&sign=6a1ee16242b93a3ae6492bc55992b691', + { + headers: { + 'Accept': '*/*', + 'User-Agent': 'RemoveUserAgent', + } + })).content; + d.push({ + title: 'req结果-60wmv', + content: html, + }); + return d; + }, + 一级: async () => { + return '这是一级:' + rule.title + }, + 二级: async () => { + return '这是二级:' + rule.title + }, + 搜索: async () => { + return ['这是搜索:' + rule.title] + }, + lazy: async () => { + return '这是播放:' + rule.title + }, +}; \ No newline at end of file diff --git a/spider/js/_lib.request.js b/spider/js/_lib.request.js index 8e9f85be..2df7a794 100644 --- a/spider/js/_lib.request.js +++ b/spider/js/_lib.request.js @@ -1,5 +1,19 @@ const iconv = require('iconv-lite'); +function sanitizeUserAgent(headers) { + if (!headers) { + return headers; + } + const keys = Object.keys(headers); + for (const key of keys) { + if (key.toLowerCase() === 'user-agent' && headers[key] === 'RemoveUserAgent') { + delete headers[key]; + break; + } + } + return headers; +} + async function requestHtml(url, options) { try { let html = (await req(url, options)).content; @@ -30,15 +44,20 @@ async function getPublicIp() { async function getHtml(config) { try { - return await axios.request(typeof config === "string" ? config : { + if (typeof config === "string") { + return await axios.request(config) + } + const cfg = { url: config.url, method: config.method || 'GET', headers: config.headers || { 'User-Agent': PC_UA }, data: config.data || '', - responseType: config.responseType || '',//'arraybuffer' - }) + responseType: config.responseType || '' + }; + cfg.headers = sanitizeUserAgent(cfg.headers); + return await axios.request(cfg) } catch (e) { return e.response } @@ -54,6 +73,7 @@ async function req_(reqUrl, mt, headers, data) { }, data: data || '', }; + config.headers = sanitizeUserAgent(config.headers); let res = await axios.request(config); return res.data; } @@ -68,6 +88,7 @@ async function req_encoding(reqUrl, mt, headers, encoding, data) { data: data || '', responseType: 'arraybuffer' }; + config.headers = sanitizeUserAgent(config.headers); let res = await axios.request(config); if (encoding) { res.data = iconv.decode(res.data, encoding); @@ -88,6 +109,7 @@ async function req_proxy(reqUrl, mt, headers, data) { port: "7890" } }; + config.headers = sanitizeUserAgent(config.headers); if (data) { config.data = data; } diff --git "a/spider/js/\344\272\272\344\272\272\345\275\261\350\247\206[\344\274\230].js" "b/spider/js/\344\272\272\344\272\272\345\275\261\350\247\206[\344\274\230].js" new file mode 100644 index 00000000..99611f90 --- /dev/null +++ "b/spider/js/\344\272\272\344\272\272\345\275\261\350\247\206[\344\274\230].js" @@ -0,0 +1,13 @@ +/* +@header({ + searchable: 1, + filterable: 1, + quickSearch: 0, + title: '人人影视', + author: 'EylinSir', + '类型': '影视', + lang: 'ds' +}) +*/ + +dmFyIHJ1bGUgPSB7CiAgICDnsbvlnos6ICflvbHop4YnLAogICAgYXV0aG9yOiAnRXlsaW5TaXInLAogICAgdGl0bGU6ICfkurrkurrlvbHop4YnLAogICAgaG9zdDogJ2h0dHBzOi8vcnJzcC1hcGkua2VqaXFpYW54aWFuLmNvbTo2MDQyNScsCiAgICB1cmw6ICcvYXBpLnBocC9tYWluX3Byb2dyYW0vbW92aWVzQWxsLycsCiAgICBzZWFyY2hVcmw6ICcvYXBpLnBocC9zZWFyY2gvc3ludGhldGljYWxTZWFyY2gvJywKICAgIHNlYXJjaGFibGU6IDEsCiAgICBxdWlja1NlYXJjaDogMCwKICAgIGZpbHRlcmFibGU6IDEsCiAgICB0aW1lb3V0OiAxMDAwMCwKICAgIHBsYXlfcGFyc2U6IHRydWUsCiAgICBoZWFkZXJzOiB7CiAgICAgICAgJ1VzZXItQWdlbnQnOiAncnJzcC53YW5nJywKICAgICAgICAnb3JpZ2luJzogJyonLAogICAgICAgICdzZWMtZmV0Y2gtZGVzdCc6ICdlbXB0eScsCiAgICAgICAgJ3NlYy1mZXRjaC1tb2RlJzogJ2NvcnMnLAogICAgICAgICdzZWMtZmV0Y2gtc2l0ZSc6ICdjcm9zcy1zaXRlJywKICAgICAgICAnc2VjLWNoLXVhJzogJyJOb3RfQSBCcmFuZCI7dj0iOCIsICJDaHJvbWl1bSI7dj0iMTIwIicsCiAgICAgICAgJ3NlYy1jaC11YS1tb2JpbGUnOiAnPzAnLAogICAgICAgICdzZWMtY2gtdWEtcGxhdGZvcm0nOiAnIldpbmRvd3MiJywKICAgICAgICAnQWNjZXB0JzogJ2FwcGxpY2F0aW9uL2pzb24sIHRleHQvcGxhaW4sICovKicsCiAgICAgICAgJ0NvbnRlbnQtVHlwZSc6ICdhcHBsaWNhdGlvbi9qc29uJywKICAgICAgICAnYWNjZXB0LWxhbmd1YWdlJzogJ3poLUNOJwogICAgfSwKCiAgICBwb3N0OiBhc3luYyBmdW5jdGlvbihwYXRoLCBkYXRhKSB7CiAgICAgICAgbGV0IHJlc3AgPSBhd2FpdCBfZmV0Y2godGhpcy5ob3N0ICsgcGF0aCwgewogICAgICAgICAgICBtZXRob2Q6ICdwb3N0JywKICAgICAgICAgICAgaGVhZGVyczogdGhpcy5oZWFkZXJzLAogICAgICAgICAgICBib2R5OiBKU09OLnN0cmluZ2lmeShkYXRhKSwKICAgICAgICAgICAgdGltZW91dDogdGhpcy50aW1lb3V0CiAgICAgICAgfSk7CiAgICAgICAgcmV0dXJuIEpTT04ucGFyc2UoYXdhaXQgcmVzcC50ZXh0KCkpOwogICAgfSwKCiAgICBjbGFzc19wYXJzZTogYXN5bmMgZnVuY3Rpb24oKSB7CiAgICAgICAgcmV0dXJuIHsKICAgICAgICAgICAgY2xhc3M6IFsKICAgICAgICAgICAgICAgIHsgJ3R5cGVfaWQnOiAnMScsICd0eXBlX25hbWUnOiAn55S15b2xJyB9LAogICAgICAgICAgICAgICAgeyAndHlwZV9pZCc6ICcyJywgJ3R5cGVfbmFtZSc6ICfnlLXop4bliacnIH0sCiAgICAgICAgICAgICAgICB7ICd0eXBlX2lkJzogJzMnLCAndHlwZV9uYW1lJzogJ+e7vOiJuicgfSwKICAgICAgICAgICAgICAgIHsgJ3R5cGVfaWQnOiAnNScsICd0eXBlX25hbWUnOiAn5Yqo5ryrJyB9LAogICAgICAgICAgICAgICAgeyAndHlwZV9pZCc6ICc0JywgJ3R5cGVfbmFtZSc6ICfnuqrlvZXniYcnIH0sCiAgICAgICAgICAgICAgICB7ICd0eXBlX2lkJzogJzYnLCAndHlwZV9uYW1lJzogJ+efreWJpycgfSwKICAgICAgICAgICAgICAgIHsgJ3R5cGVfaWQnOiAnNycsICd0eXBlX25hbWUnOiAn54m55Yir6IqC55uuJyB9LAogICAgICAgICAgICAgICAgeyAndHlwZV9pZCc6ICc4JywgJ3R5cGVfbmFtZSc6ICflsJHlhL/lhoXlrrknIH0KICAgICAgICAgICAgXSwKICAgICAgICAgICAgZmlsdGVyczoge30KICAgICAgICB9OwogICAgfSwKCiAgICDmjqjojZA6IGFzeW5jIGZ1bmN0aW9uKHRpZCwgcGcsIGZpbHRlciwgZXh0ZW5kKSB7CiAgICAgICAgcmV0dXJuIGF3YWl0IHRoaXMu5LiA57qnKHRpZCwgcGcsIGZpbHRlciwgZXh0ZW5kKTsKICAgIH0sCgogICAg5LiA57qnOiBhc3luYyBmdW5jdGlvbih0aWQsIHBnLCBmaWx0ZXIsIGV4dGVuZCkgewogICAgICAgIGlmICghdGhpcy5ob3N0KSByZXR1cm4gc2V0UmVzdWx0KFtdKTsKICAgICAgICBsZXQgZGF0YSA9IGF3YWl0IHRoaXMucG9zdCgnL2FwaS5waHAvbWFpbl9wcm9ncmFtL21vdmllc0FsbC8nLCB7CiAgICAgICAgICAgICd0eXBlJzogdGlkLCAnc29ydCc6ICd2b2RfdGltZScsICdwYWdlJzogcGcsICdsaW1pdCc6ICc2MCcsCiAgICAgICAgICAgICdhcmVhJzogJycsICdzdHlsZSc6ICcnLCAndGltZSc6ICcnLCAncGF5JzogJycKICAgICAgICB9KTsKICAgICAgICBsZXQgdmlkZW9zID0gdGhpcy5hcnIydm9kcyhkYXRhLmRhdGEubGlzdCkubWFwKGl0ZW0gPT4gKHsKICAgICAgICAgICAgdGl0bGU6IGl0ZW0udm9kX25hbWUsCiAgICAgICAgICAgIHVybDogYCR7dGhpcy5ob3N0fS9hcGkucGhwL3BsYXllci9kZXRhaWxzLz9pZD0ke2l0ZW0udm9kX2lkfWAsCiAgICAgICAgICAgIGRlc2M6IGl0ZW0udm9kX3JlbWFya3MsCiAgICAgICAgICAgIHBpY191cmw6IGl0ZW0udm9kX3BpYywKICAgICAgICAgICAgdm9kX3llYXI6IGl0ZW0udm9kX3llYXIKICAgICAgICB9KSk7CiAgICAgICAgcmV0dXJuIHNldFJlc3VsdCh2aWRlb3MpOwogICAgfSwKCiAgICDkuoznuqc6IGFzeW5jIGZ1bmN0aW9uKCkgewogICAgICAgIGxldCBpZCA9IHRoaXMuaW5wdXQubWF0Y2goL2lkPShcZCspLylbMV07CiAgICAgICAgbGV0IGRhdGEgPSBhd2FpdCB0aGlzLnBvc3QoJy9hcGkucGhwL3BsYXllci9kZXRhaWxzLycsIHsgJ2lkJzogaWQgfSk7CiAgICAgICAgbGV0IGQgPSBkYXRhLmRldGFpbERhdGE7CiAgICAgICAgcmV0dXJuIGQgPyB7CiAgICAgICAgICAgIHZvZF9pZDogZC52b2RfaWQudG9TdHJpbmcoKSwKICAgICAgICAgICAgdm9kX25hbWU6IGQudm9kX25hbWUsCiAgICAgICAgICAgIHZvZF9waWM6IGQudm9kX3BpYywKICAgICAgICAgICAgdm9kX3JlbWFya3M6IGQudm9kX3JlbWFya3MsCiAgICAgICAgICAgIHZvZF95ZWFyOiBkLnZvZF95ZWFyLAogICAgICAgICAgICB2b2RfYXJlYTogZC52b2RfYXJlYSwKICAgICAgICAgICAgdm9kX2FjdG9yOiBkLnZvZF9hY3RvciwKICAgICAgICAgICAgdm9kX2NvbnRlbnQ6IGQudm9kX2NvbnRlbnQsCiAgICAgICAgICAgIHZvZF9wbGF5X2Zyb206IGQudm9kX3BsYXlfZnJvbSwKICAgICAgICAgICAgdm9kX3BsYXlfdXJsOiBkLnZvZF9wbGF5X3VybCwKICAgICAgICAgICAgdHlwZV9uYW1lOiBkLnZvZF9jbGFzcwogICAgICAgIH0gOiB7fTsKICAgIH0sCgogICAg5pCc57SiOiBhc3luYyBmdW5jdGlvbigpIHsKICAgICAgICBsZXQgZGF0YSA9IGF3YWl0IHRoaXMucG9zdCgnL2FwaS5waHAvc2VhcmNoL3N5bnRoZXRpY2FsU2VhcmNoLycsIHsgJ2tleXdvcmQnOiB0aGlzLktFWSB9KTsKICAgICAgICBsZXQgbGlzdCA9IFsKICAgICAgICAgICAgLi4uKGRhdGEuZGF0YS5jaGFzaW5nRmFuQ29ycmVsYXRpb24gfHwgW10pLCAKICAgICAgICAgICAgLi4uKGRhdGEuZGF0YS5tb3ZpZXNDb3JyZWxhdGlvbiB8fCBbXSkKICAgICAgICBdOwogICAgICAgIGxldCByZXN1bHQgPSB0aGlzLmFycjJ2b2RzKGxpc3QpLm1hcChpdGVtID0+ICh7CiAgICAgICAgICAgIHRpdGxlOiBpdGVtLnZvZF9uYW1lLAogICAgICAgICAgICB1cmw6IGAke3RoaXMuaG9zdH0vYXBpLnBocC9wbGF5ZXIvZGV0YWlscy8/aWQ9JHtpdGVtLnZvZF9pZH1gLAogICAgICAgICAgICBkZXNjOiBpdGVtLnZvZF9yZW1hcmtzLAogICAgICAgICAgICBwaWNfdXJsOiBpdGVtLnZvZF9waWMsCiAgICAgICAgICAgIHZvZF95ZWFyOiBpdGVtLnZvZF95ZWFyCiAgICAgICAgfSkpOwogICAgICAgIHJldHVybiBzZXRSZXN1bHQocmVzdWx0KTsKICAgIH0sCgogICAgbGF6eTogYXN5bmMgZnVuY3Rpb24oKSB7CiAgICAgICAgbGV0IHVybCA9IHRoaXMuaW5wdXQ7CiAgICAgICAgbGV0IGp4ID0gMDsKICAgICAgICBjb25zdCB2aXBSZWdleCA9IC8oPzp3d3dcLmlxaXlpfHZcLnFxfHZcLnlvdWt1fHd3d1wubWd0dnx3d3dcLmJpbGliaWxpKVwuY29tLzsKICAgICAgICB0cnkgewogICAgICAgICAgICBsZXQgZGF0YSA9IGF3YWl0IHRoaXMucG9zdCgnL2FwaS5waHAvcGxheWVyL3BheVZpZGVvVXJsLycsIHsgJ3VybCc6IHVybCB9KTsKICAgICAgICAgICAgbGV0IHBsYXlfdXJsID0gZGF0YS5kYXRhLnVybDsKICAgICAgICAgICAgaWYgKHBsYXlfdXJsICYmIHBsYXlfdXJsLnN0YXJ0c1dpdGgoJ2h0dHAnKSkgewogICAgICAgICAgICAgICAgdXJsID0gcGxheV91cmw7CiAgICAgICAgICAgIH0KICAgICAgICB9IGNhdGNoIChlKSB7fQogICAgICAgIGlmICh2aXBSZWdleC50ZXN0KHVybCkpIGp4ID0gMTsKCiAgICAgICAgcmV0dXJuIHsKICAgICAgICAgICAgcGFyc2U6IGp4LAogICAgICAgICAgICB1cmw6IHVybCwKICAgICAgICAgICAgaGVhZGVyOiB7CiAgICAgICAgICAgICAgICAnVXNlci1BZ2VudCc6ICdycnNwLndhbmcnLAogICAgICAgICAgICAgICAgJ29yaWdpbic6ICcqJywKICAgICAgICAgICAgICAgICdyZWZlcmVyJzogJ2h0dHBzOi8vZG9jcy5xcS5jb20vJwogICAgICAgICAgICB9CiAgICAgICAgfTsKICAgIH0sCgogICAgYXJyMnZvZHM6IGZ1bmN0aW9uKGFycikgewogICAgICAgIHJldHVybiAoYXJyIHx8IFtdKS5tYXAoaSA9PiAoewogICAgICAgICAgICAndm9kX2lkJzogaS52b2RfaWQudG9TdHJpbmcoKSwKICAgICAgICAgICAgJ3ZvZF9uYW1lJzogaS52b2RfbmFtZSwKICAgICAgICAgICAgJ3ZvZF9waWMnOiBpLnZvZF9waWMsCiAgICAgICAgICAgICd2b2RfcmVtYXJrcyc6IGkudm9kX3NlcmlhbCA9PT0gJzEnID8gJzHpm4YnIDogYOivhOWIhu+8miR7aS52b2Rfc2NvcmUgfHwgaS52b2RfZG91YmFuX3Njb3JlIHx8ICcnfWAsCiAgICAgICAgICAgICd2b2RfeWVhcic6IGkudm9kX3llYXIgfHwgbnVsbAogICAgICAgIH0pKTsKICAgIH0KfTs= \ No newline at end of file diff --git "a/spider/js/\345\205\211\347\244\276\346\274\253\347\224\273[\347\224\273].js" "b/spider/js/\345\205\211\347\244\276\346\274\253\347\224\273[\347\224\273].js" new file mode 100644 index 00000000..7e7a3948 --- /dev/null +++ "b/spider/js/\345\205\211\347\244\276\346\274\253\347\224\273[\347\224\273].js" @@ -0,0 +1,89 @@ +/* +@header({ + searchable: 2, + filterable: 0, + quickSearch: 0, + title: '光社漫画', + author: 'EylinSir', + '类型': '漫画', + logo: 'https://m.g-mh.org/favicon.ico', + lang: 'ds' +}) +*/ + +var rule = { + 类型: '漫画', + author: 'EylinSir', + title: '光社漫画', + host: 'https://m.g-mh.org', + url: '/manga-genre/fyclass/page/fypage', + searchUrl: '/s/**?page=fypage', + logo: 'https://m.g-mh.org/favicon.ico', + searchable: 2, + quickSearch: 0, + timeout: 5000, + play_parse: true, + class_name: '热门&国漫&韩漫&日漫&欧美&其他', + class_url: 'hots&cn&kr&jp&ou-mei&qita', + headers: { + 'User-Agent': 'PC_UA', + 'Referer': 'https://m.g-mh.org/' + }, + + _parse: function(html) { + return (html.match(//g) || []).map(it => { + let img = it.match(/src=["']([^"']+)["']/)[1]; + let descMatch = it.match(/

([\s\S]*?)<\/p>/); + let originalImgUrl = img.startsWith('http') ? img : this.host + img; + let jpgImgUrl = 'https://wsrv.nl/?url=' + encodeURIComponent(originalImgUrl) + '&output=jpg'; + return { + title: it.match(/]*>([\s\S]*?)<\/h3>/)[1].trim(), + img: jpgImgUrl, + url: it.match(/href=["']([^"']+)["']/)[1], + desc: descMatch ? descMatch[1].trim() : '' + }; + }); + }, + + 一级: async function(tid, pg, filter, extend) { + return setResult(this._parse(await request(this.input, { headers: this.headers }))); + }, + + 推荐: async function(tid, pg, filter, extend) { + return setResult(this._parse(await request(this.input, { headers: this.headers }))); + }, + + 二级: async function(ids) { + let html = await request(this.input, { headers: this.headers }); + let mid = (html.match(/data-mid=["'](\d+)["']/) || html.match(/mid\s*:\s*["']?(\d+)["']?/))[1]; + let json = JSON.parse(await request(`https://api-get-v3.mgsearcher.com/api/manga/get?mid=${mid}&mode=all`, { headers: this.headers })); + let chapters = json.data.chapters || json.data.data.chapters; + return { + vod_id: ids[0], + vod_name: pdfh(html, 'h1&&Text'), + vod_pic: pdfh(html, '.rounded-lg img&&src'), + vod_content: pdfh(html, '.text-medium&&Text'), + type_name: "漫画", + vod_play_from: "光社漫画", + vod_play_url: chapters.map(ch => { + return `${ch.attributes?.title || 'Chapter ' + ch.id}$https://api-get-v3.mgsearcher.com/api/chapter/getinfo?m=${mid}&c=${ch.id}`; + }).join("#") + }; + }, + + 搜索: async function(wd, quick, pg) { + return setResult(this._parse(await request(this.input, { headers: this.headers }))); + }, + + lazy: async function(flag, id, flags) { + let data = JSON.parse(await request(id, { headers: this.headers })); + let images = data.data.info.images.images.map(img => + img.url.startsWith('http') ? img.url : "https://f40-1-4.g-mh.online" + img.url + ); + return { + parse: 0, + url: "pics://" + images.join("&&"), + header: this.headers + }; + } +}; diff --git "a/spider/js/\345\211\247\346\265\267\345\275\261\350\247\206[\344\274\230].js" "b/spider/js/\345\211\247\346\265\267\345\275\261\350\247\206[\344\274\230].js" new file mode 100644 index 00000000..8cfc9a21 --- /dev/null +++ "b/spider/js/\345\211\247\346\265\267\345\275\261\350\247\206[\344\274\230].js" @@ -0,0 +1,13 @@ +/* +@header({ + searchable: 1, + filterable: 1, + quickSearch: 0, + title: '剧海影视', + author: 'EylinSir', + '类型': '影视', + lang: 'ds' +}) +*/ + +dmFyIHJ1bGUgPSB7CiAgICDnsbvlnos6ICflvbHop4YnLAogICAgYXV0aG9yOiAnRXlsaW5TaXInLAogICAgdGl0bGU6ICfliafmtbflvbHop4YnLAogICAgaG9zdDogJ2h0dHA6Ly9wcXlzZHEuZ3h0dGtlamkuY246MjAyNicsCiAgICBob21lVXJsOiAnaHR0cDovL3p4eXMuZ2FvemhvdWtqLmNuJywKICAgIHVybDogJy9wdWJsaWMvP3NlcnZpY2U9QXBwLk1vdi5HZXRPbmxpbmVMaXN0JnR5cGVfaWQ9ZnljbGFzcyZwYWdlPWZ5cGFnZSZsaW1pdD0xOCcsCiAgICBzZWFyY2hVcmw6ICcvcHVibGljLz9zZXJ2aWNlPUFwcC5Nb3YuU2VhcmNoVm9kJmtleT0qKicsCiAgICBzZWFyY2hhYmxlOiAxLAogICAgcXVpY2tTZWFyY2g6IDAsCiAgICBmaWx0ZXJhYmxlOiAxLAogICAgdGltZW91dDogMTAwMDAsCiAgICBwbGF5X3BhcnNlOiB0cnVlLAogICAga2V5OiAnJywKICAgIGl2OiAnMTIzNDU2Nzg5MDEyMzQ1NicsCiAgICBoZWFkZXJzOiB7CiAgICAgICAgJ1VzZXItQWdlbnQnOiAnb2todHRwLzMuMTIuMCcsCiAgICAgICAgJ0Nvbm5lY3Rpb24nOiAnS2VlcC1BbGl2ZScsCiAgICAgICAgJ0FjY2VwdC1FbmNvZGluZyc6ICdnemlwJwogICAgfSwKICAgIAogICAgY2xhc3NfcGFyc2U6IGFzeW5jIGZ1bmN0aW9uICgpIHsKICAgICAgICBpZiAoIXRoaXMuaG9zdCkgcmV0dXJuIHsgY2xhc3M6IFtdLCBmaWx0ZXJzOiB7fSB9OwogICAgICAgIHJldHVybiB7CiAgICAgICAgICAgIGNsYXNzOiBbCiAgICAgICAgICAgICAgICB7ICd0eXBlX2lkJzogJzEnLCAndHlwZV9uYW1lJzogJ+eUteW9sScgfSwKICAgICAgICAgICAgICAgIHsgJ3R5cGVfaWQnOiAnMicsICd0eXBlX25hbWUnOiAn6L+e57ut5YmnJyB9LAogICAgICAgICAgICAgICAgeyAndHlwZV9pZCc6ICczJywgJ3R5cGVfbmFtZSc6ICfnu7zoibonIH0sCiAgICAgICAgICAgICAgICB7ICd0eXBlX2lkJzogJzQnLCAndHlwZV9uYW1lJzogJ+WKqOa8qycgfQogICAgICAgICAgICBdLAogICAgICAgICAgICBmaWx0ZXJzOiB7fQogICAgICAgIH07CiAgICB9LAoKICAgIOmihOWkhOeQhjogYXN5bmMgZnVuY3Rpb24gKCkgewogICAgICAgIHRyeSB7CiAgICAgICAgICAgIGxldCByZXNwMSA9IGF3YWl0IF9mZXRjaChgJHt0aGlzLmhvc3R9L3B1YmxpYy8/c2VydmljZT1BcHAuTW92LkdldFR5cGVMaXN0YCwgeyBoZWFkZXJzOiB0aGlzLmhlYWRlcnMgfSk7CiAgICAgICAgICAgIGxldCB0ZXh0MSA9IGF3YWl0IHJlc3AxLnRleHQoKTsKICAgICAgICAgICAgaWYgKHRleHQxLnN0YXJ0c1dpdGgoJ++7vycpKSB0ZXh0MSA9IHRleHQxLnN1YnN0cmluZygxKTsKICAgICAgICAgICAgbGV0IGRhdGExID0gSlNPTi5wYXJzZSh0ZXh0MSk7CiAgICAgICAgICAgIGxldCBzaWduX3N0YXJ0ID0gKGRhdGExLkRhdGEgfHwgW10pLmZpbmQoaSA9PiBpLnR5cGVfaWQudG9TdHJpbmcoKSA9PT0gJzEnKT8udHlwZV91bmlvbiB8fCAnJzsKICAgICAgICAgICAgbGV0IHJlc3AyID0gYXdhaXQgX2ZldGNoKGAke3RoaXMuaG9zdH0vcHVibGljLz9zZXJ2aWNlPUFwcC5Nb3YuR2V0QWRUeXBlYCwgeyBoZWFkZXJzOiB0aGlzLmhlYWRlcnMgfSk7CiAgICAgICAgICAgIGxldCB0ZXh0MiA9IGF3YWl0IHJlc3AyLnRleHQoKTsKICAgICAgICAgICAgaWYgKHRleHQyLnN0YXJ0c1dpdGgoJ++7vycpKSB0ZXh0MiA9IHRleHQyLnN1YnN0cmluZygxKTsKICAgICAgICAgICAgbGV0IGRhdGEyID0gSlNPTi5wYXJzZSh0ZXh0Mik7CiAgICAgICAgICAgIGxldCBzaWduX2VuZCA9IGRhdGEyLkRhdGEudG1wIHx8ICcnOwogICAgICAgICAgICBsZXQgZnVsbEtleSA9IHNpZ25fc3RhcnQgKyBzaWduX2VuZDsKICAgICAgICAgICAgaWYgKGZ1bGxLZXkubGVuZ3RoID49IDE2KSB7CiAgICAgICAgICAgICAgICB0aGlzLmtleSA9IGZ1bGxLZXkuc3Vic3RyaW5nKDAsIDE2KTsKICAgICAgICAgICAgfQogICAgICAgIH0gY2F0Y2ggKGUpIHsKICAgICAgICAgICAgY29uc29sZS5lcnJvcign5Yid5aeL5YyWIEtleSDlpLHotKU6JywgZSk7CiAgICAgICAgICAgIHRoaXMuaG9zdCA9ICcnOwogICAgICAgIH0KICAgIH0sCiAgICAKICAgIOaOqOiNkDogYXN5bmMgZnVuY3Rpb24gKCkgewogICAgICAgIGlmICghdGhpcy5ob3N0KSByZXR1cm4gc2V0UmVzdWx0KFtdKTsKICAgICAgICBsZXQgcmVzcCA9IGF3YWl0IF9mZXRjaChgJHt0aGlzLmhvc3R9L3B1YmxpYy8/c2VydmljZT1BcHAuTW92LkdldEhvbWVMZXZlbGAsIHsgaGVhZGVyczogdGhpcy5oZWFkZXJzIH0pOwogICAgICAgIGxldCB0ZXh0ID0gYXdhaXQgcmVzcC50ZXh0KCk7CiAgICAgICAgaWYgKHRleHQuc3RhcnRzV2l0aCgn77u/JykpIHRleHQgPSB0ZXh0LnN1YnN0cmluZygxKTsKICAgICAgICBsZXQgZGF0YSA9IEpTT04ucGFyc2UodGV4dCk7CiAgICAgICAgCiAgICAgICAgbGV0IHZpZGVvcyA9IHR5cGVvZiBkYXRhID09PSAnb2JqZWN0JyAmJiBkYXRhICE9PSBudWxsID8gCiAgICAgICAgICAgIE9iamVjdC52YWx1ZXMoZGF0YSkKICAgICAgICAgICAgICAgIC5maWx0ZXIoaXRlbSA9PiB0eXBlb2YgaXRlbSA9PT0gJ29iamVjdCcgJiYgaXRlbSAhPT0gbnVsbCkKICAgICAgICAgICAgICAgIC5mbGF0TWFwKGl0ZW0gPT4gT2JqZWN0LnZhbHVlcyhpdGVtKSkKICAgICAgICAgICAgICAgIC5maWx0ZXIoQXJyYXkuaXNBcnJheSkKICAgICAgICAgICAgICAgIC5mbGF0TWFwKGxpc3QgPT4gbGlzdC5tYXAoayA9PiAoewogICAgICAgICAgICAgICAgICAgIHRpdGxlOiBrLnZvZF9uYW1lLAogICAgICAgICAgICAgICAgICAgIHVybDogYCR7dGhpcy5ob3N0fS9wdWJsaWMvP3NlcnZpY2U9QXBwLk1vdi5HZXRPbmxpbmVNdkJ5SWQmdm9kaWQ9JHtrLnZvZF9pZH1gLAogICAgICAgICAgICAgICAgICAgIGRlc2M6IGsudm9kX3JlbWFya3MsCiAgICAgICAgICAgICAgICAgICAgcGljX3VybDogay52b2RfcGljLAogICAgICAgICAgICAgICAgICAgIHZvZF95ZWFyOiBrLnZvZF95ZWFyLAogICAgICAgICAgICAgICAgICAgIGNvbnRlbnQ6IGsudm9kX2NvbnRlbnQKICAgICAgICAgICAgICAgIH0pKSkKICAgICAgICAgICAgOiBbXTsKICAgICAgICAKICAgICAgICByZXR1cm4gc2V0UmVzdWx0KHZpZGVvcyk7CiAgICB9LAogICAgCiAgICDkuIDnuqc6IGFzeW5jIGZ1bmN0aW9uICh0aWQsIHBnLCBmaWx0ZXIsIGV4dGVuZCkgewogICAgICAgIGlmICghdGhpcy5ob3N0KSByZXR1cm4gc2V0UmVzdWx0KFtdKTsKICAgICAgICBsZXQgdXJsID0gYCR7dGhpcy5ob3N0fS9wdWJsaWMvP3NlcnZpY2U9QXBwLk1vdi5HZXRPbmxpbmVMaXN0JnR5cGVfaWQ9JHt0aWR9JnBhZ2U9JHtwZ30mbGltaXQ9MThgOwogICAgICAgIGxldCByZXNwID0gYXdhaXQgX2ZldGNoKHVybCwgeyBoZWFkZXJzOiB0aGlzLmhlYWRlcnMgfSk7CiAgICAgICAgbGV0IHRleHQgPSBhd2FpdCByZXNwLnRleHQoKTsKICAgICAgICBpZiAodGV4dC5zdGFydHNXaXRoKCfvu78nKSkgdGV4dCA9IHRleHQuc3Vic3RyaW5nKDEpOwogICAgICAgIGxldCBkYXRhID0gSlNPTi5wYXJzZSh0ZXh0KTsKICAgICAgICBsZXQgdmlkZW9zID0gKGRhdGEuRGF0YSB8fCBbXSkubWFwKGkgPT4gKHsKICAgICAgICAgICAgdGl0bGU6IGkudm9kX25hbWUsCiAgICAgICAgICAgIHVybDogYCR7dGhpcy5ob3N0fS9wdWJsaWMvP3NlcnZpY2U9QXBwLk1vdi5HZXRPbmxpbmVNdkJ5SWQmdm9kaWQ9JHtpLnZvZF9pZH1gLAogICAgICAgICAgICBkZXNjOiBpLnZvZF9yZW1hcmtzLAogICAgICAgICAgICBwaWNfdXJsOiBpLnZvZF9waWMsCiAgICAgICAgICAgIHZvZF95ZWFyOiBpLnZvZF95ZWFyLAogICAgICAgICAgICBjb250ZW50OiBpLnZvZF9jb250ZW50CiAgICAgICAgfSkpOwogICAgICAgIHJldHVybiBzZXRSZXN1bHQodmlkZW9zKTsKICAgIH0sCiAgICAKICAgIOS6jOe6pzogYXN5bmMgZnVuY3Rpb24gKCkgewogICAgICAgIGxldCB2b2RJZCA9IHRoaXMuaW5wdXQubWF0Y2goL3ZvZGlkPShcZCspLylbMV07CiAgICAgICAgbGV0IHVybCA9IGAke3RoaXMuaG9zdH0vcHVibGljLz9zZXJ2aWNlPUFwcC5Nb3YuR2V0T25saW5lTXZCeUlkJnZvZGlkPSR7dm9kSWR9YDsKICAgICAgICBsZXQgcmVzcCA9IGF3YWl0IF9mZXRjaCh1cmwsIHsgaGVhZGVyczogdGhpcy5oZWFkZXJzIH0pOwogICAgICAgIGxldCB0ZXh0ID0gYXdhaXQgcmVzcC50ZXh0KCk7CiAgICAgICAgaWYgKHRleHQuc3RhcnRzV2l0aCgn77u/JykpIHRleHQgPSB0ZXh0LnN1YnN0cmluZygxKTsKICAgICAgICBsZXQgZGF0YSA9IEpTT04ucGFyc2UodGV4dCk7CiAgICAgICAgbGV0IGZpcnN0SXRlbSA9IChkYXRhLkRhdGEgfHwgW10pLmZpbmQoaSA9PiB0eXBlb2YgaSA9PT0gJ29iamVjdCcgJiYgaSAhPT0gbnVsbCk7CiAgICAgICAgCiAgICAgICAgaWYgKGZpcnN0SXRlbSkgewogICAgICAgICAgICByZXR1cm4gewogICAgICAgICAgICAgICAgdm9kX2lkOiBmaXJzdEl0ZW0udm9kX2lkLnRvU3RyaW5nKCksCiAgICAgICAgICAgICAgICB2b2RfbmFtZTogZmlyc3RJdGVtLnZvZF9uYW1lLAogICAgICAgICAgICAgICAgdm9kX3BpYzogZmlyc3RJdGVtLnZvZF9waWMsCiAgICAgICAgICAgICAgICB2b2RfcmVtYXJrczogZmlyc3RJdGVtLnZvZF9yZW1hcmtzLAogICAgICAgICAgICAgICAgdm9kX3llYXI6IGZpcnN0SXRlbS52b2RfeWVhciwKICAgICAgICAgICAgICAgIHZvZF9hcmVhOiBmaXJzdEl0ZW0udm9kX2FyZWEsCiAgICAgICAgICAgICAgICB2b2RfYWN0b3I6IGZpcnN0SXRlbS52b2RfYWN0b3IsCiAgICAgICAgICAgICAgICB2b2RfY29udGVudDogZmlyc3RJdGVtLnZvZF9jb250ZW50LAogICAgICAgICAgICAgICAgdm9kX3BsYXlfZnJvbTogZmlyc3RJdGVtLnZvZF9wbGF5X2Zyb20sCiAgICAgICAgICAgICAgICB2b2RfcGxheV91cmw6IGZpcnN0SXRlbS52b2RfcGxheV91cmwsCiAgICAgICAgICAgICAgICB0eXBlX25hbWU6IGZpcnN0SXRlbS52b2RfY2xhc3MKICAgICAgICAgICAgfTsKICAgICAgICB9CiAgICAgICAgCiAgICAgICAgcmV0dXJuIHt9OwogICAgfSwKICAgIAogICAg5pCc57SiOiBhc3luYyBmdW5jdGlvbiAoKSB7CiAgICAgICAgaWYgKCF0aGlzLmhvc3QpIHJldHVybiBzZXRSZXN1bHQoW10pOwogICAgICAgIGxldCB1cmwgPSBgJHt0aGlzLmhvc3R9L3B1YmxpYy8/c2VydmljZT1BcHAuTW92LlNlYXJjaFZvZCZrZXk9JHtlbmNvZGVVUklDb21wb25lbnQodGhpcy5LRVkpfWA7CiAgICAgICAgbGV0IHJlc3AgPSBhd2FpdCBfZmV0Y2godXJsLCB7IGhlYWRlcnM6IHRoaXMuaGVhZGVycyB9KTsKICAgICAgICBsZXQgdGV4dCA9IGF3YWl0IHJlc3AudGV4dCgpOwogICAgICAgIGlmICh0ZXh0LnN0YXJ0c1dpdGgoJ++7vycpKSB0ZXh0ID0gdGV4dC5zdWJzdHJpbmcoMSk7CiAgICAgICAgbGV0IGRhdGEgPSBKU09OLnBhcnNlKHRleHQpOwogICAgICAgIGxldCB2aWRlb3MgPSAoZGF0YS5EYXRhIHx8IFtdKS5tYXAoaSA9PiAoewogICAgICAgICAgICB0aXRsZTogaS52b2RfbmFtZSwKICAgICAgICAgICAgdXJsOiBgJHt0aGlzLmhvc3R9L3B1YmxpYy8/c2VydmljZT1BcHAuTW92LkdldE9ubGluZU12QnlJZCZ2b2RpZD0ke2kudm9kX2lkfWAsCiAgICAgICAgICAgIGRlc2M6IGkudm9kX3JlbWFya3MsCiAgICAgICAgICAgIHBpY191cmw6IGkudm9kX3BpYywKICAgICAgICAgICAgdm9kX3llYXI6IGkudm9kX3llYXIsCiAgICAgICAgICAgIGNvbnRlbnQ6IGkudm9kX2NvbnRlbnQKICAgICAgICB9KSk7CiAgICAgICAgcmV0dXJuIHNldFJlc3VsdCh2aWRlb3MpOwogICAgfSwKICAgIAogICAgbGF6eTogYXN5bmMgZnVuY3Rpb24gKCkgewogICAgICAgIGxldCBqeCA9IDA7CiAgICAgICAgbGV0IHVybCA9ICcnOwogICAgICAgIGxldCB1YSA9ICdjb20uZ2prai56eHlzZHEvMS4xLjAgKExpbnV4O0FuZHJvaWQgMTIpIEV4b1BsYXllckxpYi8yLjEyLjMnOwogICAgICAgIGxldCBpZCA9IHRoaXMuaW5wdXQ7CiAgICAgICAgaWYgKGlkLm1hdGNoKC9eaHR0cHM/OlwvXC8uKlwuKG0zdTh8bXA0fGZsdnxta3YpL2kpKSB7CiAgICAgICAgICAgIHVybCA9IGlkOwogICAgICAgIH0gZWxzZSB7CiAgICAgICAgICAgIHRyeSB7CiAgICAgICAgICAgICAgICBsZXQgcmVzcCA9IGF3YWl0IF9mZXRjaChgJHt0aGlzLmhvc3R9L3B1YmxpYy8/c2VydmljZT1BcHAuTW92LkdldE12SlhVcmxCeVVybCZ1cmw9JHtpZH1gLCB7IGhlYWRlcnM6IHRoaXMuaGVhZGVycyB9KTsKICAgICAgICAgICAgICAgIGxldCB0ZXh0ID0gYXdhaXQgcmVzcC50ZXh0KCk7CiAgICAgICAgICAgICAgICBpZiAodGV4dC5zdGFydHNXaXRoKCfvu78nKSkgdGV4dCA9IHRleHQuc3Vic3RyaW5nKDEpOwogICAgICAgICAgICAgICAgbGV0IGRhdGEgPSBKU09OLnBhcnNlKHRleHQpOwogICAgICAgICAgICAgICAgbGV0IHJhd191cmwgPSBkYXRhLkRhdGEudXJsOwogICAgICAgICAgICAgICAgLy8g5bCd6K+V5L2/55SoQ3J5cHRvSlPov5vooYxBRVPop6Plr4YKICAgICAgICAgICAgICAgIGlmICh0aGlzLmtleSkgewogICAgICAgICAgICAgICAgICAgIHRyeSB7CiAgICAgICAgICAgICAgICAgICAgICAgIGxldCBrZXkgPSBDcnlwdG9KUy5lbmMuVXRmOC5wYXJzZSh0aGlzLmtleSk7CiAgICAgICAgICAgICAgICAgICAgICAgIGxldCBpdiA9IENyeXB0b0pTLmVuYy5VdGY4LnBhcnNlKHRoaXMuaXYpOwogICAgICAgICAgICAgICAgICAgICAgICBsZXQgZGVjcnlwdGVkVXJsID0gQ3J5cHRvSlMuQUVTLmRlY3J5cHQocmF3X3VybCwga2V5LCB7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICBpdiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1vZGU6IENyeXB0b0pTLm1vZGUuQ0JDLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgcGFkZGluZzogQ3J5cHRvSlMucGFkLlBrY3M1CiAgICAgICAgICAgICAgICAgICAgICAgIH0pLnRvU3RyaW5nKENyeXB0b0pTLmVuYy5VdGY4KTsKICAgICAgICAgICAgICAgICAgICAgICAgdXJsID0gZGVjcnlwdGVkVXJsICYmIGRlY3J5cHRlZFVybC5zdGFydHNXaXRoKCdodHRwJykgPyBkZWNyeXB0ZWRVcmwgOiB1cmw7CiAgICAgICAgICAgICAgICAgICAgfSBjYXRjaCAoZSkgewogICAgICAgICAgICAgICAgICAgICAgICBjb25zb2xlLmVycm9yKCdBRVPop6Plr4blpLHotKU6JywgZSk7CiAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgfQogICAgICAgICAgICB9IGNhdGNoIChlKSB7CiAgICAgICAgICAgICAgICBpZiAoLyg/Ond3d1wuaXFpeWl8dlwucXF8dlwueW91a3V8d3d3XC5tZ3R2fHd3d1wuYmlsaWJpbGkpXC5jb20vLnRlc3QoaWQpKSB7CiAgICAgICAgICAgICAgICAgICAgdXJsID0gaWQ7CiAgICAgICAgICAgICAgICAgICAganggPSAxOwogICAgICAgICAgICAgICAgICAgIHVhID0gTU9CSUxFX1VBOwogICAgICAgICAgICAgICAgfQogICAgICAgICAgICB9CiAgICAgICAgfQogICAgICAgIAogICAgICAgIHJldHVybiB7CiAgICAgICAgICAgIHBhcnNlOiBqeCwKICAgICAgICAgICAgdXJsLAogICAgICAgICAgICBoZWFkZXI6IHsgJ1VzZXItQWdlbnQnOiB1YSB9CiAgICAgICAgfTsKICAgIH0KfTs= \ No newline at end of file diff --git "a/spider/js/\345\212\250\346\274\253\345\225\246[\347\224\273].js" "b/spider/js/\345\212\250\346\274\253\345\225\246[\347\224\273].js" new file mode 100644 index 00000000..f8afcdd1 --- /dev/null +++ "b/spider/js/\345\212\250\346\274\253\345\225\246[\347\224\273].js" @@ -0,0 +1,105 @@ +/* +@header({ + searchable: 2, + filterable: 0, + quickSearch: 0, + title: '动漫啦', + author: 'EylinSir', + '类型': '漫画', + logo: 'https://www.dongman.la/favicon.ico', + lang: 'ds' +}) +*/ + +var rule = { + 类型: '漫画', + author: 'EylinSir', + title: '动漫啦', + host: 'https://www.dongman.la', + url: '/manhua/fyclass/fypage.html', + searchUrl: '/manhua/so/**/fypage.html', + logo: 'https://www.dongman.la/favicon.ico', + searchable: 2, + quickSearch: 0, + timeout: 5000, + limit: 20, + play_parse: true, + class_name: '日本&国产&港台&欧美&韩漫&完结&连载中', + class_url: 'japan&guochan&hongkongtaiwan&oumei&hanguo&finish&serial', + headers: { + 'User-Agent': 'PC_UA', + 'Referer': 'https://www.dongman.la/', + }, + + _parseList: function(html) { + return pdfa(html, '.cy_list_mh ul').map(ul => { + let title = pdfh(ul, 'li a.pic img&&alt'); + let href = pdfh(ul, 'li a.pic&&href'); + if (!title || !href) return null; + let img = pdfh(ul, 'li a.pic img&&src'); + return { + title: title.replace(/(漫画|在线观看)/g, '').trim(), + img: img.startsWith('//') ? 'https:' + img : img, + desc: pdfh(ul, '.updata&&Text').replace('最新:', '').trim(), + url: href, + year: pdfh(ul, '.zuozhe&&Text').replace('状态:', '').trim() + }; + }).filter(Boolean); + }, + + 推荐: async function(tid, pg, filter, extend) { + return await this.一级(tid, pg, filter, extend); + }, + + 一级: async function(tid, pg, filter, extend) { + let url = this.input; + return setResult(this._parseList(await request(url))); + }, + + 二级: async function(ids) { + let url = this.input; + let html = await request(url); + let playUrls = pdfa(html, '#play_0 li').map(it => { + let u = pdfh(it, 'a&&href'); + let t = pdfh(it, 'a&&Text'); + return (u && !u.includes('javascript')) ? t + '$' + u : null; + }).filter(Boolean); + return { + vod_name: pdfh(html, '.detail-info-title&&Text'), + vod_pic: pdfh(html, 'img.pic&&src'), + vod_content: pdfh(html, '#comic-description&&Text').replace(/(详细简介↓|收起↑)/g, "").trim(), + vod_play_from: "动漫啦", + vod_play_url: playUrls.reverse().join('#'), + vod_area: (pdfh(html, '.cy_xinxi&&Text').match(/地区:(\S+)/) || [])[1] || '', + vod_director: pdfh(html, '.detail-info-author&&Text').replace('作者:', '').trim() + }; + }, + + 搜索: async function(wd, quick, pg) { + let url = this.input; + return setResult(this._parseList(await request(url))); + }, + + lazy: async function(flag, id, flags) { + let url = this.input; + let header = { 'User-Agent': this.headers['User-Agent'], 'Referer': url }; + let imgs = []; + let tryUrls = [url.replace(/\.html$/, '') + '/all.html', url]; + for (let u of tryUrls) { + let html = await request(u); + let regex = /(?:data-original|data-src|src)=["']([^"']+\.(?:jpg|png|jpeg|webp|bmp))/gi; + let match; + while ((match = regex.exec(html)) !== null) { + let src = match[1]; + if (/logo|icon|loading|hm\.baidu/.test(src)) continue; + if (src.startsWith('//')) src = 'https:' + src; + else if (src.startsWith('/')) src = this.host + src; + if (!imgs.includes(src)) imgs.push(src); + } + if (imgs.length > 0) break; + } + if (imgs.length > 0) { + return { parse: 0, url: 'pics://' + imgs.join('&&'), header: header }; + } + } +}; \ No newline at end of file diff --git "a/spider/js/\346\230\237\350\276\260\345\275\261\351\231\242.js" "b/spider/js/\346\230\237\350\276\260\345\275\261\351\231\242[\344\274\230].js" similarity index 100% rename from "spider/js/\346\230\237\350\276\260\345\275\261\351\231\242.js" rename to "spider/js/\346\230\237\350\276\260\345\275\261\351\231\242[\344\274\230].js" diff --git "a/spider/js_bad/\346\234\250\345\205\256[\344\274\230].js" "b/spider/js/\346\234\250\345\205\256[\344\274\230].js" similarity index 88% rename from "spider/js_bad/\346\234\250\345\205\256[\344\274\230].js" rename to "spider/js/\346\234\250\345\205\256[\344\274\230].js" index 918c35da..59cac191 100644 --- "a/spider/js_bad/\346\234\250\345\205\256[\344\274\230].js" +++ "b/spider/js/\346\234\250\345\205\256[\344\274\230].js" @@ -20,7 +20,19 @@ var rule = { quickSearch: 1, filterable: 0, headers: { - 'User-Agent': MOBILE_UA + 'User-Agent': MOBILE_UA, + 'Accept': 'application/json, text/plain, */*', + 'accept-language': 'zh-CN,zh;q=0.9', + 'cache-control': 'no-cache', + 'pragma': 'no-cache', + 'priority': 'u=1, i', + 'sec-ch-ua': '"Google Chrome";v="143", "Chromium";v="143", "Not A(Brand";v="24"', + 'sec-ch-ua-mobile': '?0', + 'sec-ch-ua-platform': '"Windows"', + 'sec-fetch-dest': 'empty', + 'sec-fetch-mode': 'cors', + 'sec-fetch-site': 'same-origin', + 'x-platform': 'web' }, play_parse: true, search_match: true, diff --git "a/spider/js/\346\242\250\345\233\255\350\241\214[\346\210\217].js" "b/spider/js/\346\242\250\345\233\255\350\241\214[\346\210\217].js" new file mode 100644 index 00000000..7204c5db --- /dev/null +++ "b/spider/js/\346\242\250\345\233\255\350\241\214[\346\210\217].js" @@ -0,0 +1,14 @@ +/* +@header({ + searchable: 1, + filterable: 1, + quickSearch: 0, + title: '梨园行戏曲', + author: 'EylinSir', + '类型': '影视', + logo: 'https://img.znds.com//uploads/new/221222/9-2212221050561N.png', + lang: 'ds' +}) +*/ + +dmFyIHJ1bGUgPSB7CiAgICDnsbvlnos6ICflvbHop4YnLAogICAgYXV0aG9yOiAnRXlsaW5TaXInLAogICAgdGl0bGU6ICfmoqjlm63ooYzmiI/mm7InLAogICAgZGVzYzogJ+aiqOWbreihjOaIj+absua6kCcsCiAgICBob3N0OiAnaHR0cHM6Ly9mbHkuZGFvcmFuLnR2JywKICAgIGhvbWVVcmw6ICdodHRwczovL2ZseS5kYW9yYW4udHYnLAogICAgdXJsOiAnL0FQSV9ST1Avc2VhcmNoL2FsYnVtL3NjcmVlbicsCiAgICBzZWFyY2hVcmw6ICcvQVBJX1JPUC9zZWFyY2gvYWxidW0vbGlzdD9rZXl3b3JkPSoqJywKICAgIGxvZ286ICdodHRwczovL2ltZy56bmRzLmNvbS8vdXBsb2Fkcy9uZXcvMjIxMjIyLzktMjIxMjIyMTA1MDU2MU4ucG5nJywKICAgIHNlYXJjaGFibGU6IDEsCiAgICBxdWlja1NlYXJjaDogMCwKICAgIGZpbHRlcmFibGU6IDEsCiAgICB0aW1lb3V0OiAxMDAwMCwKICAgIHBsYXlfcGFyc2U6IHRydWUsCiAgICBoZWFkZXJzOiB7CiAgICAgICAgJ21kNSc6ICdTa3Z5cldxSzlRSFRkQ1QxMlJoeHVuangrV3dNVGU5eTRLd2dlQVNGRGhiWWFiUlNQc2tSMFE9PScsCiAgICAgICAgJ0NvbnRlbnQtVHlwZSc6ICdhcHBsaWNhdGlvbi9qc29uOyBjaGFyc2V0PVVURi04JywKICAgICAgICAnVXNlci1BZ2VudCc6ICdva2h0dHAvMy4xMi4xMCcsCiAgICAgICAgJ0hvc3QnOiAnZmx5LmRhb3Jhbi50dicsCiAgICAgICAgJ0Nvbm5lY3Rpb24nOiAnS2VlcC1BbGl2ZScKICAgIH0sCiAgICAKICAgIHJlcXVlc3Q6IGFzeW5jIGZ1bmN0aW9uICh1cmwsIG9iaikgewogICAgICAgIG9iaiA9IG9iaiB8fCB7fTsKICAgICAgICBsZXQgcmVzcG9uc2UgPSBhd2FpdCBfZmV0Y2godXJsLCB7CiAgICAgICAgICAgIG1ldGhvZDogb2JqLm1ldGhvZCB8fCAnUE9TVCcsCiAgICAgICAgICAgIGhlYWRlcnM6IG9iai5oZWFkZXJzIHx8IHRoaXMuaGVhZGVycywKICAgICAgICAgICAgYm9keTogb2JqLmRhdGEgPyBKU09OLnN0cmluZ2lmeShvYmouZGF0YSkgOiB1bmRlZmluZWQKICAgICAgICB9KTsKICAgICAgICByZXR1cm4gcmVzcG9uc2UudGV4dCgpOwogICAgfSwKCiAgICBfZm9ybWF0X2ltZzogZnVuY3Rpb24gKGltZykgewogICAgICAgIGlmICghaW1nKSB7CiAgICAgICAgICAgIHJldHVybiAnJzsKICAgICAgICB9CiAgICAgICAgaWYgKCFpbWcuc3RhcnRzV2l0aCgnaHR0cCcpKSB7CiAgICAgICAgICAgIHJldHVybiBgaHR0cHM6Ly9vdHRwaG90by5kYW9yYW4udHYvSEQvJHtpbWd9YDsKICAgICAgICB9CiAgICAgICAgcmV0dXJuIGltZzsKICAgIH0sCiAgICAKICAgIOmihOWkhOeQhjogYXN5bmMgZnVuY3Rpb24gKCkge30sCiAgICAKICAgIGNsYXNzX3BhcnNlOiBhc3luYyBmdW5jdGlvbiAoKSB7CiAgICAgICAgbGV0IGNhdGVfbGlzdCA9IFsKICAgICAgICAgICAgeyJuIjogIuWFqOmDqCIsICJ2IjogIiJ9LAogICAgICAgICAgICB7Im4iOiAi6buE5qKF5oiPIiwgInYiOiAiaG14In0sIHsibiI6ICLkuqzliaciLCAidiI6ICJqaW5nanUifSwgeyJuIjogIuabsuWJpyIsICJ2IjogInF1anUifSwKICAgICAgICAgICAgeyJuIjogIuenpuiFlCIsICJ2IjogInFpbnEifSwgeyJuIjogIua9ruWJpyIsICJ2IjogImNoYW9qdSJ9LCB7Im4iOiAi5rKq5YmnIiwgInYiOiAiaHVqdSJ9LAogICAgICAgICAgICB7Im4iOiAi5piG5puyIiwgInYiOiAia3VucXUifSwgeyJuIjogIua3ruWJpyIsICJ2IjogImh1YWlqdSJ9LCB7Im4iOiAi5am65YmnIiwgInYiOiAid3VqdSJ9LAogICAgICAgICAgICB7Im4iOiAi5rKz5Y2X5aSn6byT5LmmIiwgInYiOiAiaG5kZ3MifSwgeyJuIjogIua7h+WJpyIsICJ2IjogImRpYW5qdSJ9LCB7Im4iOiAi6ICB5bm05aSn5a2mIiwgInYiOiAiV0sifSwKICAgICAgICAgICAgeyJuIjogIue7jeWJpyIsICJ2IjogInNoYW9qdiJ9LCB7Im4iOiAi5puy6Im65pma5LyaIiwgInYiOiAiZWxzZSJ9LCB7Im4iOiAi55qu5b2x5oiPIiwgInYiOiAicHl4In0sCiAgICAgICAgICAgIHsibiI6ICLlm5vlubPosIMiLCAidiI6ICJzcGQifSwgeyJuIjogIuWQleWJpyIsICJ2IjogImx2anYifSwgeyJuIjogIuafs+eQtOaIjyIsICJ2IjogImxpdXF4In0sCiAgICAgICAgICAgIHsibiI6ICLojobku5nmiI8iLCAidiI6ICJweHgifSwgeyJuIjogIuWum+aihiIsICJ2IjogIndiIn0sIHsibiI6ICLplKHliaciLCAidiI6ICJ4aWp1In0sCiAgICAgICAgICAgIHsibiI6ICLlpKflubPosIMiLCAidiI6ICJkcGQifSwgeyJuIjogIuivneWJpyIsICJ2IjogImh1YWp1In0sIHsibiI6ICLopb/np6bmiI8iLCAidiI6ICJ4cXgifSwKICAgICAgICAgICAgeyJuIjogIuW3neWJpyIsICJ2IjogImNodWFuanUifSwgeyJuIjogIui1o+WJpyIsICJ2IjogInRhZ0lkIn0sIHsibiI6ICLlpKrlurfpgZPmg4UiLCAidiI6ICJ0a2RxIn0sCiAgICAgICAgICAgIHsibiI6ICLpl73liaciLCAidiI6ICJtaW5qdSJ9LCB7Im4iOiAi5qKF6Iqx5aSn6byTIiwgInYiOiAibWhkZyJ9LCB7Im4iOiAi5ZCJ5YmnIiwgInYiOiAiamlqdSJ9LAogICAgICAgICAgICB7Im4iOiAi55m95a2X5oiPIiwgInYiOiAiYnp4In0sIHsibiI6ICLosavliaciLCAidiI6ICJ5dWp1In0sIHsibiI6ICLotorliaciLCAidiI6ICJ5dWVqdSJ9LAogICAgICAgICAgICB7Im4iOiAi6K+E5YmnIiwgInYiOiAicGluZ2p1In0sIHsibiI6ICLlnaDlrZAiLCAidiI6ICJobnp6In0sIHsibiI6ICLmsrPljJfmooblrZAiLCAidiI6ICJoYmJ6In0sCiAgICAgICAgICAgIHsibiI6ICLnsqTliaciLCAidiI6ICJnZGR4In0sIHsibiI6ICLkuozlpLnlvKYiLCAidiI6ICJlangifSwgeyJuIjogIuays+WNl+eQtOS5piIsICJ2IjogImhucXMifSwKICAgICAgICAgICAgeyJuIjogIuaIj+absiIsICJ2IjogInhxIn0sIHsibiI6ICLkuozkurrlj7AiLCAidiI6ICJFUlQifSwgeyJuIjogIui2iuiwgyIsICJ2IjogInl1ZWQifSwKICAgICAgICAgICAgeyJuIjogIuS5kOiFlCIsICJ2IjogImxxIn0sIHsibiI6ICLmiazliaciLCAidiI6ICJ5YW5nanUifSwgeyJuIjogIuS6rOmfteWkp+m8kyIsICJ2IjogImp5ZGcifSwKICAgICAgICAgICAgeyJuIjogIuW9qeiwgyIsICJ2IjogImNhaWRpYW8ifSwgeyJuIjogIueQvOWJpyIsICJ2IjogInFpb25nanUifSwgeyJuIjogIuiSsuWJpyIsICJ2IjogInB1anYifSwKICAgICAgICAgICAgeyJuIjogIuilv+ays+Wkp+m8kyIsICJ2IjogInhoZGcifSwgeyJuIjogIua5mOWJpyIsICJ2IjogInhqIn0sIHsibiI6ICLpuqbnlLDkuaHpn74iLCAidiI6ICJtdHh5In0sCiAgICAgICAgICAgIHsibiI6ICLor4TkuaYiLCAidiI6ICJwaW5nc2h1In0sIHsibiI6ICLlupDliaciLCAidiI6ICJsdWp1In0sIHsibiI6ICLljZXlvKYiLCAidiI6ICJkYW54aWFuIn0sCiAgICAgICAgICAgIHsibiI6ICLoirHpvJPmiI8iLCAidiI6ICJodWFneCJ9LCB7Im4iOiAi55u45aOwIiwgInYiOiAieGlhbmcifSwgeyJuIjogIuWbm+iCoeW8piIsICJ2IjogInNneCJ9LAogICAgICAgICAgICB7Im4iOiAi5L+d5a6a6ICB6LCDIiwgInYiOiAiYmRsZCJ9LCB7Im4iOiAi5pmL5YmnIiwgInYiOiAiamluanUifSwgeyJuIjogIuWFtuS7liIsICJ2IjogIm90aGVyIn0sCiAgICAgICAgICAgIHsibiI6ICLmraPlrZfmiI8iLCAidiI6ICJ6engifSwgeyJuIjogIualmuWJpyIsICJ2IjogImNodWp1In0KICAgICAgICBdOwogICAgICAgIAogICAgICAgIHJldHVybiB7CiAgICAgICAgICAgIGNsYXNzOiBbeyAndHlwZV9uYW1lJzogJ+aIj+absueJh+W6kycsICd0eXBlX2lkJzogJ2FsbCcgfV0sCiAgICAgICAgICAgIGZpbHRlcnM6IHsKICAgICAgICAgICAgICAgICJhbGwiOiBbCiAgICAgICAgICAgICAgICAgICAgeyJrZXkiOiAic2VjdCIsICJuYW1lIjogIuabsuenjSIsICJ2YWx1ZSI6IGNhdGVfbGlzdH0sCiAgICAgICAgICAgICAgICAgICAgeyJrZXkiOiAiYXJlYSIsICJuYW1lIjogIui1hOi0uSIsICJ2YWx1ZSI6IFt7Im4iOiAi5YWo6YOoIiwgInYiOiAiMCJ9LCB7Im4iOiAi5YWN6LS5IiwgInYiOiAiMSJ9LCB7Im4iOiAiVklQIiwgInYiOiAiMiJ9XX0sCiAgICAgICAgICAgICAgICAgICAgeyJrZXkiOiAic29ydCIsICJuYW1lIjogIuaOkuW6jyIsICJ2YWx1ZSI6IFt7Im4iOiAi5pyA54OtIiwgInYiOiAiaG90In0sIHsibiI6ICLmnIDmlrAiLCAidiI6ICJvbmxpbmUifV19CiAgICAgICAgICAgICAgICBdCiAgICAgICAgICAgIH0KICAgICAgICB9OwogICAgfSwKICAgIAogICAg5o6o6I2QOiBhc3luYyBmdW5jdGlvbiAoKSB7CiAgICAgICAgcmV0dXJuIGF3YWl0IHRoaXMu5LiA57qnKCdhbGwnLCAxLCB7fSwge30pOwogICAgfSwKICAgIAogICAg5LiA57qnOiBhc3luYyBmdW5jdGlvbiAodGlkLCBwZywgZmlsdGVyLCBleHRlbmQpIHsKICAgICAgICBsZXQgdXJsID0gYCR7dGhpcy5ob3N0fS9BUElfUk9QL3NlYXJjaC9hbGJ1bS9zY3JlZW5gOwogICAgICAgIGxldCBzZWN0ID0gZXh0ZW5kPy5zZWN0IHx8ICcnOwogICAgICAgIGlmICh0aWQgPT09ICdhbGwnICYmICFzZWN0KSB7CiAgICAgICAgICAgIHNlY3QgPSAnJzsKICAgICAgICB9CiAgICAgICAgCiAgICAgICAgbGV0IHBheWxvYWQgPSB7CiAgICAgICAgICAgICJjdXIiOiBwYXJzZUludChwZyksCiAgICAgICAgICAgICJwYWdlU2l6ZSI6IDMwLAogICAgICAgICAgICAicmVzVHlwZSI6IDEsCiAgICAgICAgICAgICJzZWN0Ijogc2VjdCwKICAgICAgICAgICAgIm9yZGVyYnkiOiBleHRlbmQ/LnNvcnQgfHwgJ2hvdCcsCiAgICAgICAgICAgICJ0YWdJZCI6IDAsCiAgICAgICAgICAgICJ1c2VySWQiOiAiOTIzMTVlYzZlNThhNDViYTdmNDdmZDE0M2IzZDc5NTYiLAogICAgICAgICAgICAiY2hhbm5lbCI6ICJ2aXZvIiwKICAgICAgICAgICAgIml0ZW0iOiAieTkiLAogICAgICAgICAgICAibm9kZUNvZGUiOiAiMDAxMDAwIiwKICAgICAgICAgICAgInByb2plY3QiOiAibHloeGN4IgogICAgICAgIH07CiAgICAgICAgCiAgICAgICAgbGV0IGFyZWEgPSBleHRlbmQ/LmFyZWEgfHwgJzAnOwogICAgICAgIGlmIChhcmVhID09PSAnMScgfHwgYXJlYSA9PT0gJzInKSB7CiAgICAgICAgICAgIHBheWxvYWRbJ2ZyZWUnXSA9IHBhcnNlSW50KGFyZWEpOwogICAgICAgIH0KICAgICAgICAKICAgICAgICB0cnkgewogICAgICAgICAgICBsZXQgcmVzcCA9IGF3YWl0IHRoaXMucmVxdWVzdCh1cmwsIHsgZGF0YTogcGF5bG9hZCB9KTsKICAgICAgICAgICAgbGV0IGpzb24gPSBKU09OLnBhcnNlKHJlc3ApOwogICAgICAgICAgICBsZXQgZGF0YSA9IGpzb24ucGIgfHwganNvbi5kYXRhIHx8IHt9OwogICAgICAgICAgICBsZXQgdm9kX2xpc3QgPSBbXTsKICAgICAgICAgICAgZm9yIChsZXQgaXRlbSBvZiBkYXRhLmRhdGFMaXN0IHx8IFtdKSB7CiAgICAgICAgICAgICAgICB2b2RfbGlzdC5wdXNoKHsKICAgICAgICAgICAgICAgICAgICB0aXRsZTogaXRlbS5uYW1lLAogICAgICAgICAgICAgICAgICAgIHVybDogYCR7dGhpcy5ob3N0fS9BUElfUk9QL2FsYnVtL3Jlcy9saXN0P2FsYnVtQ29kZT0ke2l0ZW0uY29kZX1gLAogICAgICAgICAgICAgICAgICAgIGRlc2M6IChpdGVtLnB1Ymxpc2hUaW1lIHx8ICcnKS5zcGxpdCgnICcpWzBdLAogICAgICAgICAgICAgICAgICAgIHBpY191cmw6IHRoaXMuX2Zvcm1hdF9pbWcoaXRlbS5pbWdzZWMpLAogICAgICAgICAgICAgICAgICAgIHZvZF95ZWFyOiAoaXRlbS5wdWJsaXNoVGltZSB8fCAnJykuc3Vic3RyaW5nKDAsIDQpCiAgICAgICAgICAgICAgICB9KTsKICAgICAgICAgICAgfQogICAgICAgICAgICAKICAgICAgICAgICAgcmV0dXJuIHNldFJlc3VsdCh2b2RfbGlzdCk7CiAgICAgICAgfSBjYXRjaCAoZSkgewogICAgICAgICAgICBjb25zb2xlLmVycm9yKGUpOwogICAgICAgICAgICByZXR1cm4gc2V0UmVzdWx0KFtdKTsKICAgICAgICB9CiAgICB9LAogICAgCiAgICDkuoznuqc6IGFzeW5jIGZ1bmN0aW9uICgpIHsKICAgICAgICBsZXQgYWxidW1Db2RlID0gdGhpcy5pbnB1dC5tYXRjaCgvYWxidW1Db2RlPSguKj8pKD86JnwkKS8pWzFdOwogICAgICAgIGxldCB1cmwgPSBgJHt0aGlzLmhvc3R9L0FQSV9ST1AvYWxidW0vcmVzL2xpc3RgOwogICAgICAgIAogICAgICAgIGxldCBwYXlsb2FkID0gewogICAgICAgICAgICAiYWxidW1Db2RlIjogYWxidW1Db2RlLAogICAgICAgICAgICAiY3VyIjogMSwKICAgICAgICAgICAgInBhZ2VTaXplIjogNTAwLAogICAgICAgICAgICAidXNlcklkIjogIjkyMzE1ZWM2ZTU4YTQ1YmE3ZjQ3ZmQxNDNiM2Q3OTU2IiwKICAgICAgICAgICAgImNoYW5uZWwiOiAidml2byIsCiAgICAgICAgICAgICJpdGVtIjogInk5IiwKICAgICAgICAgICAgIm5vZGVDb2RlIjogIjAwMTAwMCIsCiAgICAgICAgICAgICJwcm9qZWN0IjogImx5aHhjeCIKICAgICAgICB9OwogICAgICAgIAogICAgICAgIHRyeSB7CiAgICAgICAgICAgIGxldCByZXNwID0gYXdhaXQgdGhpcy5yZXF1ZXN0KHVybCwgeyBkYXRhOiBwYXlsb2FkIH0pOwogICAgICAgICAgICBsZXQganNvbiA9IEpTT04ucGFyc2UocmVzcCk7CiAgICAgICAgICAgIGxldCBhbGJ1bSA9IGpzb24uYWxidW0gfHwge307CiAgICAgICAgICAgIGxldCB0cmFja3MgPSBqc29uLnBiPy5kYXRhTGlzdCB8fCBbXTsKICAgICAgICAgICAgdHJhY2tzLnNvcnQoKGEsIGIpID0+IHBhcnNlSW50KGEuc29ydCB8fCAwKSAtIHBhcnNlSW50KGIuc29ydCB8fCAwKSk7CiAgICAgICAgICAgIGxldCBwbGF5X3VybHMgPSBbXTsKICAgICAgICAgICAgZm9yIChsZXQgdCBvZiB0cmFja3MpIHsKICAgICAgICAgICAgICAgIGlmICh0LmNvZGUpIHsKICAgICAgICAgICAgICAgICAgICBwbGF5X3VybHMucHVzaChgJHt0Lm5hbWUucmVwbGFjZSgvXCQvZywgJ18nKX0kJHt0LmNvZGV9YCk7CiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgIH0KICAgICAgICAgICAgCiAgICAgICAgICAgIGxldCBWT0QgPSB7CiAgICAgICAgICAgICAgICB2b2RfaWQ6IGFsYnVtQ29kZSwKICAgICAgICAgICAgICAgIHZvZF9uYW1lOiBhbGJ1bS5uYW1lIHx8ICfmnKrnn6UnLAogICAgICAgICAgICAgICAgdm9kX3BpYzogdGhpcy5fZm9ybWF0X2ltZyhhbGJ1bS5pbWdzZWMpLAogICAgICAgICAgICAgICAgdHlwZV9uYW1lOiAi5oiP5puyIiwKICAgICAgICAgICAgICAgIHZvZF95ZWFyOiBhbGJ1bS5wdWJsaXNoVGltZSB8fCAnJywKICAgICAgICAgICAgICAgIHZvZF9hcmVhOiAi5Lit5Zu9IiwKICAgICAgICAgICAgICAgIHZvZF9jb250ZW50OiBhbGJ1bS5kZXMgfHwgJ+aaguaXoOeugOS7iycsCiAgICAgICAgICAgICAgICB2b2RfcGxheV9mcm9tOiAi5qKo5Zut6KGMIiwKICAgICAgICAgICAgICAgIHZvZF9wbGF5X3VybDogcGxheV91cmxzLmpvaW4oJyMnKQogICAgICAgICAgICB9OwogICAgICAgICAgICAKICAgICAgICAgICAgcmV0dXJuIFZPRDsKICAgICAgICB9IGNhdGNoIChlKSB7CiAgICAgICAgICAgIGNvbnNvbGUuZXJyb3IoZSk7CiAgICAgICAgICAgIHJldHVybiB7fTsKICAgICAgICB9CiAgICB9LAogICAgCiAgICDmkJzntKI6IGFzeW5jIGZ1bmN0aW9uICgpIHsKICAgICAgICBsZXQgdXJsID0gYCR7dGhpcy5ob3N0fS9BUElfUk9QL3NlYXJjaC9hbGJ1bS9saXN0YDsKICAgICAgICBsZXQgcGF5bG9hZCA9IHsKICAgICAgICAgICAgImN1ciI6IHBhcnNlSW50KHRoaXMuTVlfUEFHRSksCiAgICAgICAgICAgICJwYWdlU2l6ZSI6IDIwLAogICAgICAgICAgICAia2V5d29yZCI6IHRoaXMuS0VZLAogICAgICAgICAgICAiaXRlbSI6ICJ5OSIsCiAgICAgICAgICAgICJub2RlQ29kZSI6ICIwMDEwMDAiLAogICAgICAgICAgICAib3JkZXJieSI6ICJob3QiLAogICAgICAgICAgICAicHgiOiAyLAogICAgICAgICAgICAic2VjdCI6IFtdLAogICAgICAgICAgICAidXNlcklkIjogIjkyMzE1ZWM2ZTU4YTQ1YmE3ZjQ3ZmQxNDNiM2Q3OTU2IiwKICAgICAgICAgICAgInByb2plY3QiOiAibHloeGN4IgogICAgICAgIH07CiAgICAgICAgCiAgICAgICAgdHJ5IHsKICAgICAgICAgICAgbGV0IHJlc3AgPSBhd2FpdCB0aGlzLnJlcXVlc3QodXJsLCB7IGRhdGE6IHBheWxvYWQgfSk7CiAgICAgICAgICAgIGxldCBqc29uID0gSlNPTi5wYXJzZShyZXNwKTsKICAgICAgICAgICAgbGV0IGRhdGEgPSBqc29uLnBiIHx8IGpzb24uZGF0YSB8fCB7fTsKICAgICAgICAgICAgbGV0IHZvZF9saXN0ID0gW107CiAgICAgICAgICAgIGZvciAobGV0IGl0ZW0gb2YgZGF0YS5kYXRhTGlzdCB8fCBbXSkgewogICAgICAgICAgICAgICAgdm9kX2xpc3QucHVzaCh7CiAgICAgICAgICAgICAgICAgICAgdGl0bGU6IGl0ZW0ubmFtZSwKICAgICAgICAgICAgICAgICAgICB1cmw6IGAke3RoaXMuaG9zdH0vQVBJX1JPUC9hbGJ1bS9yZXMvbGlzdD9hbGJ1bUNvZGU9JHtpdGVtLmNvZGV9YCwKICAgICAgICAgICAgICAgICAgICBkZXNjOiAoaXRlbS5wdWJsaXNoVGltZSB8fCAnJyksCiAgICAgICAgICAgICAgICAgICAgcGljX3VybDogdGhpcy5fZm9ybWF0X2ltZyhpdGVtLmltZ3NlYykKICAgICAgICAgICAgICAgIH0pOwogICAgICAgICAgICB9CiAgICAgICAgICAgIHJldHVybiBzZXRSZXN1bHQodm9kX2xpc3QpOwogICAgICAgIH0gY2F0Y2ggKGUpIHsKICAgICAgICAgICAgY29uc29sZS5lcnJvcihlKTsKICAgICAgICAgICAgcmV0dXJuIHNldFJlc3VsdChbXSk7CiAgICAgICAgfQogICAgfSwKICAgIAogICAgbGF6eTogYXN5bmMgZnVuY3Rpb24gKCkgewogICAgICAgIGxldCByZXNDb2RlID0gdGhpcy5pbnB1dDsKICAgICAgICBsZXQgdXJsID0gYCR7dGhpcy5ob3N0fS9BUElfUk9QL3BsYXkvZ2V0L3BsYXl1cmxgOwogICAgICAgIGxldCBwYXlsb2FkID0gewogICAgICAgICAgICAicmVzQ29kZSI6IHJlc0NvZGUsCiAgICAgICAgICAgICJpdGVtIjogInk5IiwKICAgICAgICAgICAgIm1hc2siOiAwLAogICAgICAgICAgICAibm9kZUNvZGUiOiAiMDAxMDAwIiwKICAgICAgICAgICAgInByb2plY3QiOiAibHloeGN4IiwKICAgICAgICAgICAgInB4IjogMiwKICAgICAgICAgICAgInVzZXJJZCI6ICI5MjMxNWVjNmU1OGE0NWJhN2Y0N2ZkMTQzYjNkNzk1NiIKICAgICAgICB9OwogICAgICAgIAogICAgICAgIHRyeSB7CiAgICAgICAgICAgIGxldCByZXNwID0gYXdhaXQgdGhpcy5yZXF1ZXN0KHVybCwgeyBkYXRhOiBwYXlsb2FkIH0pOwogICAgICAgICAgICBsZXQganNvbiA9IEpTT04ucGFyc2UocmVzcCk7CiAgICAgICAgICAgIGxldCBwbGF5X3VybCA9IGpzb24ucGxheXJlcz8ucGxheXVybCB8fCAnJzsKICAgICAgICAgICAgcmV0dXJuIHsKICAgICAgICAgICAgICAgIHBhcnNlOiAwLAogICAgICAgICAgICAgICAgdXJsOiBwbGF5X3VybCwKICAgICAgICAgICAgICAgIGhlYWRlcjogeyAnVXNlci1BZ2VudCc6IHRoaXMuaGVhZGVyc1snVXNlci1BZ2VudCddIH0KICAgICAgICAgICAgfTsKICAgICAgICB9IGNhdGNoIChlKSB7CiAgICAgICAgICAgIGNvbnNvbGUuZXJyb3IoZSk7CiAgICAgICAgICAgIHJldHVybiB7CiAgICAgICAgICAgICAgICBwYXJzZTogMCwKICAgICAgICAgICAgICAgIHVybDogJycKICAgICAgICAgICAgfTsKICAgICAgICB9CiAgICB9Cn07 \ No newline at end of file diff --git "a/spider/js/\346\263\245\350\247\206\351\242\221.js" "b/spider/js/\346\263\245\350\247\206\351\242\221[\344\274\230].js" similarity index 100% rename from "spider/js/\346\263\245\350\247\206\351\242\221.js" rename to "spider/js/\346\263\245\350\247\206\351\242\221[\344\274\230].js" diff --git "a/spider/js/\347\203\255\346\222\255\345\275\261\350\247\206[\344\274\230].js" "b/spider/js/\347\203\255\346\222\255\345\275\261\350\247\206[\344\274\230].js" new file mode 100644 index 00000000..4ffd935a --- /dev/null +++ "b/spider/js/\347\203\255\346\222\255\345\275\261\350\247\206[\344\274\230].js" @@ -0,0 +1,13 @@ +/* +@header({ + searchable: 1, + filterable: 1, + quickSearch: 1, + title: '热播APP', + author: 'EylinSir', + '类型': '影视', + lang: 'ds' +}) +*/ + +dmFyIHJ1bGUgPSB7CiAgICDnsbvlnos6ICflvbHop4YnLAogICAgYXV0aG9yOiAnRXlsaW5TaXInLAogICAgdGl0bGU6ICfng63mkq1BUFAnLAogICAgZGVzYzogJ+eDreaSrUFQUOW9seinhua6kCcsCiAgICBob3N0OiAnaHR0cDovL3YucmJvdHYuY24nLAogICAgaG9tZVVybDogJ2h0dHA6Ly92LnJib3R2LmNuJywKICAgIHVybDogJy92My9ob21lL3R5cGVfc2VhcmNoJywKICAgIHNlYXJjaFVybDogJy92My9ob21lL3NlYXJjaCcsCiAgICBzZWFyY2hhYmxlOiAxLAogICAgcXVpY2tTZWFyY2g6IDEsCiAgICBmaWx0ZXJhYmxlOiAxLAogICAgdGltZW91dDogMTAwMDAsCiAgICBwbGF5X3BhcnNlOiB0cnVlLAogICAgaGVhZGVyczogewogICAgICAgICdVc2VyLUFnZW50JzogJ29raHR0cC1va2dvL2plYXNvbmx6eScsCiAgICAgICAgJ0FjY2VwdC1MYW5ndWFnZSc6ICd6aC1DTix6aDtxPTAuOCcKICAgIH0sCiAgICAKICAgIGdldGZpbGVzOiBmdW5jdGlvbiAocCA9IHt9KSB7CiAgICAgICAgbGV0IHQgPSBNYXRoLmZsb29yKERhdGUubm93KCkgLyAxMDAwKS50b1N0cmluZygpOwogICAgICAgIGxldCBzID0gbWQ1KCc3Z3AwYm5kMnNyODV5ZGlpMmozMnBjeXBzY29jNHc2YzdnNXNwbCcgKyB0KTsKICAgICAgICBsZXQgZmlsZXMgPSB7CiAgICAgICAgICAgICdzaWduJzogcywKICAgICAgICAgICAgJ3RpbWVzdGFtcCc6IHQKICAgICAgICB9OwogICAgICAgIHJldHVybiB7IC4uLnAsIC4uLmZpbGVzIH07CiAgICB9LAoKICAgIGdldHY6IGZ1bmN0aW9uIChkYXRhKSB7CiAgICAgICAgcmV0dXJuIGRhdGEuZmlsdGVyKGkgPT4gaS52b2RfaWQgJiYgaS52b2RfaWQudG9TdHJpbmcoKSAhPT0gJzAnKS5tYXAoaSA9PiAoewogICAgICAgICAgICB0aXRsZTogaS52b2RfbmFtZSwKICAgICAgICAgICAgdXJsOiBgJHt0aGlzLmhvc3R9L3YzL2hvbWUvdm9kX2RldGFpbHM/dm9kX2lkPSR7aS52b2RfaWR9YCwKICAgICAgICAgICAgZGVzYzogaS52b2RfcmVtYXJrcywKICAgICAgICAgICAgcGljX3VybDogaS52b2RfcGljIHx8IGkudm9kX3BpY190aHVtYiwKICAgICAgICAgICAgdm9kX3llYXI6IGkudGFnCiAgICAgICAgfSkpOwogICAgfSwKICAgIAogICAg6aKE5aSE55CGOiBhc3luYyBmdW5jdGlvbiAoKSB7fSwKCiAgICBjbGFzc19wYXJzZTogYXN5bmMgZnVuY3Rpb24gKCkgewogICAgICAgIGxldCBkYXRhID0gdGhpcy5nZXRmaWxlcyh7ICcnOiAnJyB9KTsKICAgICAgICBsZXQgcmVzcCA9IGF3YWl0IF9mZXRjaChgJHt0aGlzLmhvc3R9L3YzL3R5cGUvdG9wX3R5cGVgLCB7CiAgICAgICAgICAgIG1ldGhvZDogJ3Bvc3QnLAogICAgICAgICAgICBoZWFkZXJzOiB7IC4uLnRoaXMuaGVhZGVycywgJ0NvbnRlbnQtVHlwZSc6ICdhcHBsaWNhdGlvbi94LXd3dy1mb3JtLXVybGVuY29kZWQnIH0sCiAgICAgICAgICAgIGJvZHk6IG5ldyBVUkxTZWFyY2hQYXJhbXMoZGF0YSkudG9TdHJpbmcoKQogICAgICAgIH0pOwogICAgICAgIGxldCBqc29uID0gSlNPTi5wYXJzZShhd2FpdCByZXNwLnRleHQoKSk7CiAgICAgICAgbGV0IGNsYXNzZXMgPSBqc29uLmRhdGEubGlzdC5tYXAoayA9PiAoewogICAgICAgICAgICB0eXBlX2lkOiBrLnR5cGVfaWQudG9TdHJpbmcoKSwKICAgICAgICAgICAgdHlwZV9uYW1lOiBrLnR5cGVfbmFtZQogICAgICAgIH0pKTsKICAgICAgICBsZXQgZmlsdGVycyA9IHt9OwogICAgICAgIGZvciAoY29uc3QgayBvZiBqc29uLmRhdGEubGlzdCkgewogICAgICAgICAgICBsZXQgZnRzID0gW107CiAgICAgICAgICAgIGZvciAoY29uc3QgW2tleSwgdmFsdWVdIG9mIE9iamVjdC5lbnRyaWVzKGspKSB7CiAgICAgICAgICAgICAgICBpZiAoQXJyYXkuaXNBcnJheSh2YWx1ZSkgJiYgdmFsdWUubGVuZ3RoID4gMikgewogICAgICAgICAgICAgICAgICAgIGZ0cy5wdXNoKHsKICAgICAgICAgICAgICAgICAgICAgICAgbmFtZToga2V5LAogICAgICAgICAgICAgICAgICAgICAgICBrZXksCiAgICAgICAgICAgICAgICAgICAgICAgIHZhbHVlOiB2YWx1ZS5maWx0ZXIoaiA9PiBqICYmIGogIT09ICflhajpg6gnKS5tYXAoaiA9PiAoeyBuOiBqLCB2OiBqIH0pKQogICAgICAgICAgICAgICAgICAgIH0pOwogICAgICAgICAgICAgICAgfQogICAgICAgICAgICB9CiAgICAgICAgICAgIGlmIChmdHMubGVuZ3RoKSB7CiAgICAgICAgICAgICAgICBmaWx0ZXJzW2sudHlwZV9pZF0gPSBmdHM7CiAgICAgICAgICAgIH0KICAgICAgICB9CiAgICAgICAgcmV0dXJuIHsgY2xhc3M6IGNsYXNzZXMsIGZpbHRlcnMgfTsKICAgIH0sCiAgICAKICAgIOaOqOiNkDogYXN5bmMgZnVuY3Rpb24gKCkgewogICAgICAgIGxldCBkYXRhID0gdGhpcy5nZXRmaWxlcyh7ICcnOiAnJyB9KTsKICAgICAgICBsZXQgcmVzcCA9IGF3YWl0IF9mZXRjaChgJHt0aGlzLmhvc3R9L3YzL3R5cGUvdGpfdm9kYCwgewogICAgICAgICAgICBtZXRob2Q6ICdwb3N0JywKICAgICAgICAgICAgaGVhZGVyczogeyAuLi50aGlzLmhlYWRlcnMsICdDb250ZW50LVR5cGUnOiAnYXBwbGljYXRpb24veC13d3ctZm9ybS11cmxlbmNvZGVkJyB9LAogICAgICAgICAgICBib2R5OiBuZXcgVVJMU2VhcmNoUGFyYW1zKGRhdGEpLnRvU3RyaW5nKCkKICAgICAgICB9KTsKICAgICAgICBsZXQganNvbiA9IEpTT04ucGFyc2UoYXdhaXQgcmVzcC50ZXh0KCkpOwogICAgICAgIGxldCB2aWRlb3MgPSB0aGlzLmdldHYoanNvbi5kYXRhLmNhaS5jb25jYXQoanNvbi5kYXRhLmxvb3ApKTsKICAgICAgICByZXR1cm4gc2V0UmVzdWx0KHZpZGVvcyk7CiAgICB9LAogICAgCiAgICDkuIDnuqc6IGFzeW5jIGZ1bmN0aW9uICh0aWQsIHBnLCBmaWx0ZXIsIGV4dGVuZCkgewogICAgICAgIGxldCBmaWxlcyA9IHsKICAgICAgICAgICAgJ3R5cGVfaWQnOiB0aWQsCiAgICAgICAgICAgICdsaW1pdCc6ICcxMicsCiAgICAgICAgICAgICdwYWdlJzogcGcKICAgICAgICB9OwogICAgICAgIGZvciAoY29uc3QgW2ssIHZdIG9mIE9iamVjdC5lbnRyaWVzKGV4dGVuZCB8fCB7fSkpIHsKICAgICAgICAgICAgbGV0IGtleSA9IGsgPT09ICdleHRlbmQnID8gJ2NsYXNzJyA6IGs7CiAgICAgICAgICAgIGZpbGVzW2tleV0gPSB2OwogICAgICAgIH0KICAgICAgICBsZXQgZGF0YSA9IHRoaXMuZ2V0ZmlsZXMoZmlsZXMpOwogICAgICAgIGxldCByZXNwID0gYXdhaXQgX2ZldGNoKGAke3RoaXMuaG9zdH0vdjMvaG9tZS90eXBlX3NlYXJjaGAsIHsKICAgICAgICAgICAgbWV0aG9kOiAncG9zdCcsCiAgICAgICAgICAgIGhlYWRlcnM6IHsgLi4udGhpcy5oZWFkZXJzLCAnQ29udGVudC1UeXBlJzogJ2FwcGxpY2F0aW9uL3gtd3d3LWZvcm0tdXJsZW5jb2RlZCcgfSwKICAgICAgICAgICAgYm9keTogbmV3IFVSTFNlYXJjaFBhcmFtcyhkYXRhKS50b1N0cmluZygpCiAgICAgICAgfSk7CiAgICAgICAgbGV0IGpzb24gPSBKU09OLnBhcnNlKGF3YWl0IHJlc3AudGV4dCgpKTsKICAgICAgICBsZXQgdmlkZW9zID0gdGhpcy5nZXR2KGpzb24uZGF0YS5saXN0KTsKICAgICAgICByZXR1cm4gc2V0UmVzdWx0KHZpZGVvcyk7CiAgICB9LAogICAgCiAgICDkuoznuqc6IGFzeW5jIGZ1bmN0aW9uICgpIHsKICAgICAgICBsZXQgdm9kSWQgPSB0aGlzLmlucHV0Lm1hdGNoKC92b2RfaWQ9KFxkKykvKVsxXTsKICAgICAgICBsZXQgZGF0YSA9IHRoaXMuZ2V0ZmlsZXMoeyAndm9kX2lkJzogdm9kSWQgfSk7CiAgICAgICAgbGV0IHJlc3AgPSBhd2FpdCBfZmV0Y2goYCR7dGhpcy5ob3N0fS92My9ob21lL3ZvZF9kZXRhaWxzYCwgewogICAgICAgICAgICBtZXRob2Q6ICdwb3N0JywKICAgICAgICAgICAgaGVhZGVyczogeyAuLi50aGlzLmhlYWRlcnMsICdDb250ZW50LVR5cGUnOiAnYXBwbGljYXRpb24veC13d3ctZm9ybS11cmxlbmNvZGVkJyB9LAogICAgICAgICAgICBib2R5OiBuZXcgVVJMU2VhcmNoUGFyYW1zKGRhdGEpLnRvU3RyaW5nKCkKICAgICAgICB9KTsKICAgICAgICBsZXQgdiA9IEpTT04ucGFyc2UoYXdhaXQgcmVzcC50ZXh0KCkpLmRhdGE7CiAgICAgICAgbGV0IFZPRCA9IHsKICAgICAgICAgICAgdm9kX2lkOiB2LnZvZF9pZC50b1N0cmluZygpLAogICAgICAgICAgICB2b2RfbmFtZTogdi52b2RfbmFtZSwKICAgICAgICAgICAgdHlwZV9uYW1lOiB2LnR5cGVfbmFtZSwKICAgICAgICAgICAgdm9kX3llYXI6IHYudm9kX3llYXIsCiAgICAgICAgICAgIHZvZF9hcmVhOiB2LnZvZF9hcmVhLAogICAgICAgICAgICB2b2RfcmVtYXJrczogdi52b2RfcmVtYXJrcywKICAgICAgICAgICAgdm9kX2FjdG9yOiB2LnZvZF9hY3RvciwKICAgICAgICAgICAgdm9kX2RpcmVjdG9yOiB2LnZvZF9kaXJlY3RvciwKICAgICAgICAgICAgdm9kX2NvbnRlbnQ6ICh2LnZvZF9jb250ZW50IHx8ICfml6AnKS5yZXBsYWNlKC88W14+XSo+L2csICcnKS50cmltKCkKICAgICAgICB9OwogICAgICAgIGxldCBuID0gW107CiAgICAgICAgbGV0IHAgPSBbXTsKICAgICAgICBmb3IgKGNvbnN0IGkgb2Ygdi52b2RfcGxheV9saXN0KSB7CiAgICAgICAgICAgIGxldCBwYXJzZXMgPSBpLnBhcnNlX3VybHMuam9pbignLCcpOwogICAgICAgICAgICBsZXQgdXJscyA9IGkudXJscy5tYXAoaiA9PiBgJHtqLm5hbWV9JCR7ai51cmx9QCR7cGFyc2VzfUAke2kudWEgfHwgJyd9QCR7aS5yZWZlcmVyIHx8ICcnfWApOwogICAgICAgICAgICBwLnB1c2godXJscy5qb2luKCcjJykpOwogICAgICAgICAgICBsZXQgbmFtZSA9IChpLnRpdGxlIHx8IGkubmFtZSB8fCAnJykucmVwbGFjZSgvW1wo77yIXSg/OueCueWHu3zmjaIpW14pXSpbXCnvvIldfFta4pa24p2k44CQXS4qL2csICcnKTsKICAgICAgICAgICAgbi5wdXNoKG5hbWUgPT09IGkuZmxhZyA/IG5hbWUgOiBgJHtuYW1lfSgke2kuZmxhZ30pYCk7CiAgICAgICAgfQogICAgICAgIFZPRC52b2RfcGxheV9mcm9tID0gbi5qb2luKCckJCQnKTsKICAgICAgICBWT0Qudm9kX3BsYXlfdXJsID0gcC5qb2luKCckJCQnKTsKICAgICAgICByZXR1cm4gVk9EOwogICAgfSwKICAgIAogICAg5pCc57SiOiBhc3luYyBmdW5jdGlvbiAoKSB7CiAgICAgICAgbGV0IGZpbGVzID0gewogICAgICAgICAgICAnbGltaXQnOiAnMTInLAogICAgICAgICAgICAncGFnZSc6IHRoaXMuTVlfUEFHRSwKICAgICAgICAgICAgJ2tleXdvcmQnOiB0aGlzLktFWQogICAgICAgIH07CiAgICAgICAgbGV0IGRhdGEgPSB0aGlzLmdldGZpbGVzKGZpbGVzKTsKICAgICAgICBsZXQgcmVzcCA9IGF3YWl0IF9mZXRjaChgJHt0aGlzLmhvc3R9L3YzL2hvbWUvc2VhcmNoYCwgewogICAgICAgICAgICBtZXRob2Q6ICdwb3N0JywKICAgICAgICAgICAgaGVhZGVyczogeyAuLi50aGlzLmhlYWRlcnMsICdDb250ZW50LVR5cGUnOiAnYXBwbGljYXRpb24veC13d3ctZm9ybS11cmxlbmNvZGVkJyB9LAogICAgICAgICAgICBib2R5OiBuZXcgVVJMU2VhcmNoUGFyYW1zKGRhdGEpLnRvU3RyaW5nKCkKICAgICAgICB9KTsKICAgICAgICBsZXQganNvbiA9IEpTT04ucGFyc2UoYXdhaXQgcmVzcC50ZXh0KCkpOwogICAgICAgIGxldCB2aWRlb3MgPSB0aGlzLmdldHYoanNvbi5kYXRhLmxpc3QpOwogICAgICAgIHJldHVybiBzZXRSZXN1bHQodmlkZW9zKTsKICAgIH0sCiAgICAKICAgIGxhenk6IGFzeW5jIGZ1bmN0aW9uICgpIHsKICAgICAgICBsZXQgW3Jhd191cmwsIHBhcnNlc1N0ciA9ICcnLCB1YSA9ICcnLCByZWZlcmVyID0gJyddID0gdGhpcy5pbnB1dC5zcGxpdCgnQCcpOwogICAgICAgIGxldCBwYXJzZXMgPSBwYXJzZXNTdHIgPyBwYXJzZXNTdHIuc3BsaXQoJywnKSA6IFtdOwogICAgICAgIGxldCBwbGF5X2hlYWRlcnMgPSB7fTsKICAgICAgICBpZiAodWEpIHBsYXlfaGVhZGVyc1snVXNlci1BZ2VudCddID0gdWE7CiAgICAgICAgaWYgKHJlZmVyZXIpIHBsYXlfaGVhZGVyc1snUmVmZXJlciddID0gcmVmZXJlcjsKICAgICAgICBsZXQgZmluYWxfdXJsID0gJyc7CiAgICAgICAgbGV0IGp4ID0gMDsKICAgICAgICBmb3IgKGNvbnN0IHBhcnNlIG9mIHBhcnNlcykgewogICAgICAgICAgICBpZiAocGFyc2Uuc3RhcnRzV2l0aCgnaHR0cCcpKSB7CiAgICAgICAgICAgICAgICB0cnkgewogICAgICAgICAgICAgICAgICAgIGxldCByZXMgPSBhd2FpdCBfZmV0Y2gocGFyc2UgKyByYXdfdXJsLCB7IGhlYWRlcnM6IHRoaXMuaGVhZGVycyB9KTsKICAgICAgICAgICAgICAgICAgICBsZXQganNvbiA9IEpTT04ucGFyc2UoYXdhaXQgcmVzLnRleHQoKSk7CiAgICAgICAgICAgICAgICAgICAgaWYgKGpzb24udXJsICYmIGpzb24udXJsLnN0YXJ0c1dpdGgoJ2h0dHAnKSkgewogICAgICAgICAgICAgICAgICAgICAgICBmaW5hbF91cmwgPSBqc29uLnVybDsKICAgICAgICAgICAgICAgICAgICAgICAgYnJlYWs7CiAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgfSBjYXRjaCAoZSkge30KICAgICAgICAgICAgfQogICAgICAgIH0KCiAgICAgICAgaWYgKCFmaW5hbF91cmwgJiYgcmF3X3VybC5zdGFydHNXaXRoKCdodHRwJykpIHsKICAgICAgICAgICAgZmluYWxfdXJsID0gcmF3X3VybDsKICAgICAgICAgICAgaWYgKC8oPzp3d3dcLmlxaXlpfHZcLnFxfHZcLnlvdWt1fHd3d1wubWd0dnx3d3dcLmJpbGliaWxpKVwuY29tLy50ZXN0KHJhd191cmwpKSB7CiAgICAgICAgICAgICAgICBqeCA9IDE7CiAgICAgICAgICAgIH0KICAgICAgICB9CiAgICAgICAgcmV0dXJuIHsKICAgICAgICAgICAgcGFyc2U6IGp4LAogICAgICAgICAgICB1cmw6IGZpbmFsX3VybCwKICAgICAgICAgICAgaGVhZGVyOiBwbGF5X2hlYWRlcnMKICAgICAgICB9OwogICAgfQp9Ow== \ No newline at end of file diff --git "a/spider/js/\347\210\261\347\216\251\351\237\263\344\271\220[\345\220\254].js" "b/spider/js/\347\210\261\347\216\251\351\237\263\344\271\220[\345\220\254].js" new file mode 100644 index 00000000..28c15f7c --- /dev/null +++ "b/spider/js/\347\210\261\347\216\251\351\237\263\344\271\220[\345\220\254].js" @@ -0,0 +1,14 @@ +/* +@header({ + searchable: 1, + filterable: 1, + quickSearch: 1, + title: '爱玩音乐', + author: 'EylinSir', + '类型': '音乐', + logo: 'https://www.22a5.com/favicon.ico', + lang: 'ds' +}) +*/ + +dmFyIHJ1bGUgPSB7CiAgICDnsbvlnos6ICfpn7PkuZAnLAogICAgdGl0bGU6ICfniLHnjqnpn7PkuZAnLAogICAgYXV0aG9yOiAnRXlsaW5TaXInLAogICAgaG9zdDogJ2h0dHBzOi8vd3d3LjIyYTUuY29tJywKICAgIHVybDogJy9meWNsYXNzL2Z5ZmlsdGVyLWZ5cGFnZS8nLAogICAgbG9nbzogJ2h0dHBzOi8vd3d3LjIyYTUuY29tL2Zhdmljb24uaWNvJywKICAgIGNsYXNzX25hbWU6ICfnlLXlj7Am5q2M5Y2VJuS4k+i+kSbmrYzmiYsm6auY5riFTVYm5paw5q2M5qacJlRPUOamnOWNlScsCiAgICBjbGFzc191cmw6ICcvcmFkaW9saXN0L2luZGV4Lmh0bWwmL3BsYXl0eXBlL2luZGV4Lmh0bWwmL2FsYnVtbGlzdC9pbmRleC5odG1sJi9zaW5nZXJsaXN0L2luZGV4L2luZGV4L2luZGV4L2luZGV4Lmh0bWwmL212bGlzdC9odWF5dS5odG1sJi9saXN0L25ldy5odG1sJi9saXN0L3RvcC5odG1sJywKICAgIHNlYXJjaFVybDogJy9zby8qKi9meXBhZ2UuaHRtbCcsCiAgICBzZWFyY2hhYmxlOiAxLAogICAgcXVpY2tTZWFyY2g6IDEsCiAgICBmaWx0ZXJhYmxlOiAxLAogICAgdGltZW91dDogNTAwMCwKICAgIHBsYXlfcGFyc2U6IHRydWUsCiAgICBoZWFkZXJzOiB7CiAgICAgICAgJ1VzZXItQWdlbnQnOiBNT0JJTEVfVUEsCiAgICAgICAgJ1JlZmVyZXInOiAnaHR0cHM6Ly93d3cuMjJhNS5jb20vJwogICAgfSwKICAgIGZpbHRlcjogewogICAgICAgICIvcmFkaW9saXN0L2luZGV4Lmh0bWwiOiBbeyBrZXk6ICJpZCIsIG5hbWU6ICLliIbnsbsiLCB2YWx1ZTogW3sgIm4iOiAi5pyA5pawIiwgInYiOiAiaW5kZXgiIH0sIHsgIm4iOiAi5pyA54OtIiwgInYiOiAiaG90IiB9LCB7ICJuIjogIuacieWjsOWwj+ivtCIsICJ2IjogIm5vdmVsIiB9LCB7ICJuIjogIuebuOWjsCIsICJ2IjogInhpYW5neWkiIH0sIHsgIm4iOiAi6Z+z5LmQIiwgInYiOiAibXVzaWMiIH0sIHsgIm4iOiAi5oOF5oSfIiwgInYiOiAiZW1vdGlvbiIgfSwgeyAibiI6ICLlm73mvKsiLCAidiI6ICJnYW1lIiB9LCB7ICJuIjogIuW9seinhiIsICJ2IjogInlpbmdzaGkiIH0sIHsgIm4iOiAi6ISx5Y+j56eAIiwgInYiOiAidGFsa3Nob3ciIH0sIHsgIm4iOiAi5Y6G5Y+yIiwgInYiOiAiaGlzdG9yeSIgfSwgeyAibiI6ICLlhL/nq6UiLCAidiI6ICJjaGlsZHJlbiIgfSwgeyAibiI6ICLmlZnogrIiLCAidiI6ICJlZHVjYXRpb24iIH0sIHsgIm4iOiAi5YWr5Y2mIiwgInYiOiAiZ29zc2lwIiB9LCB7ICJuIjogIuaOqOeQhiIsICJ2IjogInR1aWxpIiB9LCB7ICJuIjogIuWktOadoSIsICJ2IjogImhlYWRsaW5lIiB9XSB9XSwKICAgICAgICAiL3Npbmdlcmxpc3QvaW5kZXgvaW5kZXgvaW5kZXgvaW5kZXguaHRtbCI6IFsKICAgICAgICAgICAgeyAia2V5IjogImFyZWEiLCAibmFtZSI6ICLlnLDljLoiLCAidmFsdWUiOiBbeyAibiI6ICLlhajpg6giLCAidiI6ICJpbmRleCIgfSwgeyAibiI6ICLljY7or60iLCAidiI6ICJodWF5dSIgfSwgeyAibiI6ICLmrKfnvo4iLCAidiI6ICJvdW1laSIgfSwgeyAibiI6ICLpn6nlm70iLCAidiI6ICJoYW5ndW8iIH0sIHsgIm4iOiAi5pel5pysIiwgInYiOiAicmlicm4iIH1dIH0sCiAgICAgICAgICAgIHsgImtleSI6ICJzZXgiLCAibmFtZSI6ICLmgKfliKsiLCAidmFsdWUiOiBbeyAibiI6ICLlhajpg6giLCAidiI6ICJpbmRleCIgfSwgeyAibiI6ICLnlLciLCAidiI6ICJtYWxlIiB9LCB7ICJuIjogIuWlsyIsICJ2IjogImdpcmwiIH0sIHsgIm4iOiAi57uE5ZCIIiwgInYiOiAiYmFuZCIgfV0gfSwKICAgICAgICAgICAgeyAia2V5IjogImdlbnJlIiwgIm5hbWUiOiAi5rWB5rS+IiwgInZhbHVlIjogW3sgIm4iOiAi5YWo6YOoIiwgInYiOiAiaW5kZXgiIH0sIHsgIm4iOiAi5rWB6KGMIiwgInYiOiAibGl1eGluZyIgfSwgeyAibiI6ICLnlLXlrZAiLCAidiI6ICJkaWFuemkiIH0sIHsgIm4iOiAi5pGH5ruaIiwgInYiOiAieWFvZ3VuIiB9LCB7ICJuIjogIuWYu+WTiCIsICJ2IjogInhpaGEiIH0sIHsgIm4iOiAiUiZCIiwgInYiOiAicmIiIH0sIHsgIm4iOiAi5rCR6LCjIiwgInYiOiAibWlueWFvIiB9LCB7ICJuIjogIueIteWjqyIsICJ2IjogImp1ZXNoaSIgfSwgeyAibiI6ICLlj6TlhbgiLCAidiI6ICJndWRpYW4iIH1dIH0sCiAgICAgICAgICAgIHsgImtleSI6ICJjaGFyIiwgIm5hbWUiOiAi5a2X5q+NIiwgInZhbHVlIjogW3sgIm4iOiAi5YWo6YOoIiwgInYiOiAiaW5kZXgiIH0sIHsgIm4iOiAiQSIsICJ2IjogImEiIH0sIHsgIm4iOiAiQiIsICJ2IjogImIiIH0sIHsgIm4iOiAiQyIsICJ2IjogImMiIH0sIHsgIm4iOiAiRCIsICJ2IjogImQiIH0sIHsgIm4iOiAiRSIsICJ2IjogImUiIH0sIHsgIm4iOiAiRiIsICJ2IjogImYiIH0sIHsgIm4iOiAiRyIsICJ2IjogImciIH0sIHsgIm4iOiAiSCIsICJ2IjogImgiIH0sIHsgIm4iOiAiSSIsICJ2IjogImkiIH0sIHsgIm4iOiAiSiIsICJ2IjogImoiIH0sIHsgIm4iOiAiSyIsICJ2IjogImsiIH0sIHsgIm4iOiAiTCIsICJ2IjogImwiIH0sIHsgIm4iOiAiTSIsICJ2IjogIm0iIH0sIHsgIm4iOiAiTiIsICJ2IjogIm4iIH0sIHsgIm4iOiAiTyIsICJ2IjogIm8iIH0sIHsgIm4iOiAiUCIsICJ2IjogInAiIH0sIHsgIm4iOiAiUSIsICJ2IjogInEiIH0sIHsgIm4iOiAiUiIsICJ2IjogInIiIH0sIHsgIm4iOiAiUyIsICJ2IjogInMiIH0sIHsgIm4iOiAiVCIsICJ2IjogInQiIH0sIHsgIm4iOiAiVSIsICJ2IjogInUiIH0sIHsgIm4iOiAiViIsICJ2IjogInYiIH0sIHsgIm4iOiAiVyIsICJ2IjogInciIH0sIHsgIm4iOiAiWCIsICJ2IjogIngiIH0sIHsgIm4iOiAiWSIsICJ2IjogInkiIH0sIHsgIm4iOiAiWiIsICJ2IjogInoiIH1dIH0KICAgICAgICBdLAogICAgICAgICIvbXZsaXN0L2h1YXl1Lmh0bWwiOiBbeyAia2V5IjogImlkIiwgIm5hbWUiOiAi57G75Z6LIiwgInZhbHVlIjogW3sgIm4iOiAi5Y2O6K+t57K+6YCJIiwgInYiOiAiaHVheXUiIH0sIHsgIm4iOiAi5pel6Z+p57K+6YCJIiwgInYiOiAiIHJpaGFuIiB9LCB7ICJuIjogIuasp+e+jueyvumAiSIsICJ2IjogIm91bWVpIiB9XSB9XQogICAgfSwKCiAgICBfYWJzOiAodXJsKSA9PiB7CiAgICAgICAgaWYgKCF1cmwgfHwgdHlwZW9mIHVybCAhPT0gJ3N0cmluZycpIHJldHVybiAnJzsKICAgICAgICByZXR1cm4gdXJsLnN0YXJ0c1dpdGgoJ2h0dHAnKSA/IHVybCA6IGBodHRwczovL3d3dy4yMmE1LmNvbSR7dXJsLnN0YXJ0c1dpdGgoJy8nKSA/IHVybCA6ICcvJyArIHVybH1gOwogICAgfSwKCiAgICBfY2xlYW46ICh0ZXh0KSA9PiAodGV4dCB8fCAnJykucmVwbGFjZSgvKOeIseeOqemfs+S5kOe9kXzop4bpopHkuIvovb3or7TmmI586KeG6aKR5LiL6L295Zyw5Z2AfHd3d1wuMnQ1OFwuY29tfE1QM+WFjei0ueS4i+i9vXxMUkPmrYzor43kuIvovb185YWo6YOo5q2M5puyfFxb56ysXGQr6aG1XF185Yi35pawfOavj+aXpeaOqOiNkHzmnIDmlrB854Ot6ZeofOaOqOiNkHxNVnzpq5jmuIV85peg5o2fKS9naSwgJycpLnRyaW0oKSwKCiAgICBfZ2V0X2l0ZW1zOiBmdW5jdGlvbihodG1sLCBwZGZhLCBwZCwgcGRmaCkgewogICAgICAgIGlmICh0eXBlb2YgaHRtbCAhPT0gJ3N0cmluZycpIHJldHVybiBbXTsKICAgICAgICBjb25zdCBzZWxlY3RvcnMgPSBbJy52aWRlb19saXN0IGxpJywgJy5wbGF5X2xpc3QgbGknLCAnLnBpY19saXN0IGxpJywgJy5zaW5nZXJfbGlzdCBsaScsICcuYWxpIGxpJywgJy5sYXl1aS1yb3cgbGknLCAnLmJhc2VfbCBsaScsICcubGlzdCBsaScsICcuaXRlbSBsaSddOwogICAgICAgIGxldCBpdGVtcyA9IFtdOwogICAgICAgIGZvciAobGV0IHNlbCBvZiBzZWxlY3RvcnMpIHsKICAgICAgICAgICAgbGV0IGxpc3QgPSBwZGZhKGh0bWwsIHNlbCkgfHwgW107CiAgICAgICAgICAgIGlmIChsaXN0Lmxlbmd0aCA9PT0gMCkgY29udGludWU7CiAgICAgICAgICAgIGZvciAobGV0IGl0IG9mIGxpc3QpIHsKICAgICAgICAgICAgICAgIGxldCBocmVmID0gcGQoaXQsICdhJiZocmVmJyk7CiAgICAgICAgICAgICAgICBpZiAoIWhyZWYgfHwgaHJlZiA9PT0gJy8nIHx8IC8oXC91c2VyXC98XC9sb2dpblwvfGphdmFzY3JpcHQpLy50ZXN0KGhyZWYpKSBjb250aW51ZTsKICAgICAgICAgICAgICAgIGxldCB0aXRsZSA9IHRoaXMuX2NsZWFuKHBkZmgoaXQsICcubmFtZSYmVGV4dCcpIHx8IHBkKGl0LCAnYSYmdGl0bGUnKSB8fCBwZChpdCwgJ2EmJlRleHQnKSk7CiAgICAgICAgICAgICAgICBpZiAoIXRpdGxlKSBjb250aW51ZTsKICAgICAgICAgICAgICAgIGxldCBpbWdfc3JjID0gcGQoaXQsICdpbWcmJmRhdGEtb3JpZ2luYWwnKSB8fCBwZChpdCwgJ2ltZyYmZGF0YS1zcmMnKSB8fCBwZChpdCwgJ2ltZyYmc3JjJyk7CiAgICAgICAgICAgICAgICBsZXQgcGljX3VybCA9IHRoaXMuX2FicygoaW1nX3NyYyB8fCAnJykucmVwbGFjZSgnMTIwJywgJzUwMCcpKTsKICAgICAgICAgICAgICAgIGl0ZW1zLnB1c2goewogICAgICAgICAgICAgICAgICAgIHRpdGxlOiB0aXRsZSwKICAgICAgICAgICAgICAgICAgICB1cmw6IHRoaXMuX2FicyhocmVmKSwKICAgICAgICAgICAgICAgICAgICBwaWNfdXJsOiBwaWNfdXJsLAogICAgICAgICAgICAgICAgICAgIGRlc2M6ICcnCiAgICAgICAgICAgICAgICB9KTsKICAgICAgICAgICAgfQogICAgICAgIH0KICAgICAgICByZXR1cm4gaXRlbXM7CiAgICB9LAoKICAgIOaOqOiNkDogYXN5bmMgZnVuY3Rpb24oKSB7CiAgICAgICAgcmV0dXJuIFtdOwogICAgfSwKCiAgICDkuIDnuqc6IGFzeW5jIGZ1bmN0aW9uKHRpZCwgcGcsIGZpbHRlciwgZXh0ZW5kKSB7CiAgICAgICAgbGV0IHBhZ2UgPSBwZyB8fCAxOwogICAgICAgIGxldCB1cmwgPSB0aWQucmVwbGFjZSgvXC9cLy9nLCAnLycpLnJlcGxhY2UoJy9meWZpbHRlci0xLycsICcnKTsKICAgICAgICBpZiAoL1wvc2luZ2VybGlzdFwvLy50ZXN0KHRpZCkpIHsKICAgICAgICAgICAgbGV0IHAgPSB0aWQuc3BsaXQoJy8nKTsKICAgICAgICAgICAgaWYgKHAubGVuZ3RoID49IDIpIHsKICAgICAgICAgICAgICAgIGxldCBhcmVhID0gZXh0ZW5kLmFyZWEgfHwgcFsyXSB8fCAnaW5kZXgnOwogICAgICAgICAgICAgICAgbGV0IHNleCA9IGV4dGVuZC5zZXggfHwgcFszXSB8fCAnaW5kZXgnOwogICAgICAgICAgICAgICAgbGV0IGdlbnJlID0gZXh0ZW5kLmdlbnJlIHx8IHBbNF0gfHwgJ2luZGV4JzsKICAgICAgICAgICAgICAgIGxldCBjaGFyID0gZXh0ZW5kLmNoYXIgfHwgJ2luZGV4JzsKICAgICAgICAgICAgICAgIHVybCA9IGAvJHtwWzFdfS8ke2FyZWF9LyR7c2V4fS8ke2dlbnJlfS8ke2NoYXJ9Lmh0bWxgOwogICAgICAgICAgICB9CiAgICAgICAgfQoKICAgICAgICBpZiAoZXh0ZW5kLmlkICYmICFbJ2luZGV4JywgJ3RvcCddLmluY2x1ZGVzKGV4dGVuZC5pZCkpIHsKICAgICAgICAgICAgdXJsID0gdXJsLnJlcGxhY2UoLyhcdyspXC5odG1sJC8sIGV4dGVuZC5pZCArICcuaHRtbCcpOwogICAgICAgIH0KCiAgICAgICAgaWYgKHBhZ2UgPiAxKSB7CiAgICAgICAgICAgIGxldCBzZXAgPSAvKFwvc2luZ2VybGlzdFwvfFwvcmFkaW9saXN0XC98XC9tdmxpc3RcL3xcL3BsYXl0eXBlXC98XC9saXN0XC8pLy50ZXN0KHVybCkgPyAnLycgOiAnXyc7CiAgICAgICAgICAgIHVybCA9IHVybC5yZXBsYWNlKC8oX1xkK3xcL1xkKyk/XC5odG1sJC8sIGAke3NlcH0ke3BhZ2V9Lmh0bWxgKTsKICAgICAgICB9CiAgICAgICAgCiAgICAgICAgbGV0IGZpbmFsVXJsID0gdGhpcy5fYWJzKHVybCk7CiAgICAgICAgbGV0IHJlc3BvbnNlID0gYXdhaXQgcmVxKGZpbmFsVXJsKTsKICAgICAgICByZXR1cm4gc2V0UmVzdWx0KHRoaXMuX2dldF9pdGVtcyhyZXNwb25zZS5jb250ZW50IHx8IHJlc3BvbnNlLCB0aGlzLnBkZmEsIHRoaXMucGQsIHRoaXMucGRmaCkpOwogICAgfSwKCiAgICDmkJzntKI6IGFzeW5jIGZ1bmN0aW9uKHdkLCBxdWljaywgcGcpIHsKICAgICAgICBsZXQgcGFnZSA9IHBnIHx8IDE7CiAgICAgICAgbGV0IHVybCA9IHRoaXMuX2FicyhgL3NvLyR7ZW5jb2RlVVJJQ29tcG9uZW50KHdkKX0vJHtwYWdlfS5odG1sYCk7CiAgICAgICAgbGV0IHJlc3BvbnNlID0gYXdhaXQgcmVxKHVybCk7CiAgICAgICAgcmV0dXJuIHNldFJlc3VsdCh0aGlzLl9nZXRfaXRlbXMocmVzcG9uc2UuY29udGVudCB8fCByZXNwb25zZSwgdGhpcy5wZGZhLCB0aGlzLnBkLCB0aGlzLnBkZmgpKTsKICAgIH0sCgogICAg5LqM57qnOiBhc3luYyBmdW5jdGlvbihpZHMpIHsKICAgICAgICBjb25zdCB7IHBkZmEsIHBkZmgsIHBkIH0gPSB0aGlzOwogICAgICAgIGxldCB1cmwgPSAoQXJyYXkuaXNBcnJheShpZHMpID8gaWRzWzBdIDogaWRzKS5yZXBsYWNlKC9bYCBdL2csICcnKTsKICAgICAgICB1cmwgPSB0aGlzLl9hYnModXJsKTsKICAgICAgICAKICAgICAgICBsZXQgaHRtbCA9IChhd2FpdCByZXEodXJsKSkuY29udGVudDsKICAgICAgICBsZXQgY292ZXIgPSB0aGlzLl9hYnMocGQoaHRtbCwgJy5zaW5nZXJfaW5mbyAucGljIGltZyYmc3JjJykpOwogICAgICAgIGxldCBjb250ZW50ID0gcGRmaChodG1sLCAnLnNpbmdlcl9pbmZvIC5saXN0X3IgLmluZm8gcCYmVGV4dCcpOwogICAgICAgIAogICAgICAgIGxldCB2b2QgPSB7CiAgICAgICAgICAgIHZvZF9uYW1lOiB0aGlzLl9jbGVhbihwZGZoKGh0bWwsICdoMSYmVGV4dCcpIHx8IHBkZmgoaHRtbCwgJ3RpdGxlJiZUZXh0JykpLAogICAgICAgICAgICB2b2RfcGljOiBjb3ZlciwKICAgICAgICAgICAgdm9kX3BsYXlfZnJvbTogJ+eIseeOqemfs+S5kCcsCiAgICAgICAgICAgIHZvZF9jb250ZW50OiBjb250ZW50IHx8ICcnLAogICAgICAgICAgICB2b2RfcmVtYXJrczogJycKICAgICAgICB9OwogICAgICAgIAogICAgICAgIGxldCBwbGF5X2xpc3QgPSBbXTsKICAgICAgICBsZXQgbWFrZUlkID0gKHUsIGxyYyA9ICcnKSA9PiBgJHt1fXx8fCR7Y292ZXJ9fHx8JHtscmN9YDsKICAgICAgICBsZXQgZ2V0THJjTGluayA9IChsaW5rKSA9PiB7CiAgICAgICAgICAgIGxldCBtYXRjaCA9IGxpbmsubWF0Y2goL1wvKHNvbmd8bXAzfHJhZGlvfHJhZGlvbGlzdHxyYWRpb3BsYXkpXC8oW14vXSspXC5odG1sLyk7CiAgICAgICAgICAgIHJldHVybiBtYXRjaCA/IHRoaXMuX2FicyhgL3BsdWcvZG93bi5waHA/YWM9bXVzaWMmbGs9bHJjJmlkPSR7bWF0Y2hbMl19YCkgOiAnJzsKICAgICAgICB9OwoKICAgICAgICBpZiAoL1wvKHZpZGVvfG1wNClcLyhbXi9dKylcLmh0bWwvLnRlc3QodXJsKSkgewogICAgICAgICAgICBsZXQgdmlkID0gdXJsLm1hdGNoKC9cLyh2aWRlb3xtcDQpXC8oW14vXSspXC5odG1sLylbMl07CiAgICAgICAgICAgIGxldCBxdWFsaXRpZXMgPSBbeyBuYW1lOiAn6JOd5YWJJywgcTogJzEwODAnIH0sIHsgbmFtZTogJ+i2hea4hScsIHE6ICc3MjAnIH0sIHsgbmFtZTogJ+mrmOa4hScsIHE6ICc0ODAnIH1dOwogICAgICAgICAgICBxdWFsaXRpZXMuZm9yRWFjaChxID0+IHsKICAgICAgICAgICAgICAgIHBsYXlfbGlzdC5wdXNoKGAke3EubmFtZX0kJHttYWtlSWQodGhpcy5fYWJzKGAvcGx1Zy9kb3duLnBocD9hYz12cGxheSZpZD0ke3ZpZH0mcT0ke3EucX1gKSl9YCk7CiAgICAgICAgICAgIH0pOwogICAgICAgIH0gCiAgICAgICAgZWxzZSB7CiAgICAgICAgICAgIGxldCBpdGVtcyA9IHBkZmEoaHRtbCwgJy5wbGF5X2xpc3QmJmxpJykgfHwgW107CiAgICAgICAgICAgIGlmICghaXRlbXMubGVuZ3RoKSBpdGVtcyA9IHBkZmEoaHRtbCwgJy5zb25nX2xpc3QmJmxpJykgfHwgW107CiAgICAgICAgICAgIGlmICghaXRlbXMubGVuZ3RoKSBpdGVtcyA9IHBkZmEoaHRtbCwgJy5tdXNpY19saXN0JiZsaScpIHx8IFtdOwogICAgICAgICAgICBpZiAoIWl0ZW1zLmxlbmd0aCkgaXRlbXMgPSBwZGZhKGh0bWwsICd1bCYmbGknKSB8fCBbXTsKICAgICAgICAgICAgaWYgKGl0ZW1zLmxlbmd0aCA9PT0gMCAmJiAvXC8oc29uZ3xtcDN8cmFkaW98cmFkaW9saXN0fHJhZGlvcGxheSlcLy8udGVzdCh1cmwpKSB7CiAgICAgICAgICAgICAgICBwbGF5X2xpc3QucHVzaChg5pKt5pS+JCR7bWFrZUlkKHVybCwgZ2V0THJjTGluayh1cmwpKX1gKTsKICAgICAgICAgICAgfSAKICAgICAgICAgICAgZWxzZSB7CiAgICAgICAgICAgICAgICBpdGVtcy5mb3JFYWNoKGl0ID0+IHsKICAgICAgICAgICAgICAgICAgICBsZXQgYSA9IHBkKGl0LCAnYSYmaHJlZicpOwogICAgICAgICAgICAgICAgICAgIGlmICghYSkgcmV0dXJuOwogICAgICAgICAgICAgICAgICAgIGlmICgvXC8oc29uZ3xtcDN8cmFkaW98cmFkaW9saXN0fHJhZGlvcGxheXx2aWRlb3xtcDQpXC8vLnRlc3QoYSkpIHsKICAgICAgICAgICAgICAgICAgICAgICAgbGV0IHRpdGxlID0gdGhpcy5fY2xlYW4ocGRmaChpdCwgJy5uYW1lJiZUZXh0JykgfHwgcGQoaXQsICdhJiZUZXh0JykpOwogICAgICAgICAgICAgICAgICAgICAgICBwbGF5X2xpc3QucHVzaChgJHt0aXRsZX0kJHttYWtlSWQodGhpcy5fYWJzKGEpLCBnZXRMcmNMaW5rKGEpKX1gKTsKICAgICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICB9KTsKICAgICAgICAgICAgfQogICAgICAgIH0KICAgICAgICB2b2Qudm9kX3BsYXlfdXJsID0gcGxheV9saXN0LmpvaW4oJyMnKTsKICAgICAgICByZXR1cm4gdm9kOwogICAgfSwKCiAgICBsYXp5OiBhc3luYyBmdW5jdGlvbihmbGFnLCBpZCwgZmxhZ3MpIHsKICAgICAgICBsZXQgW3VybCwgcGljLCBscmNVcmxdID0gaWQuc3BsaXQoJ3x8fCcpOwogICAgICAgIHVybCA9ICh1cmwgfHwgJycpLnJlcGxhY2UoL0AuKyQvLCAnJykudHJpbSgpOwogICAgICAgIGxldCBhcGlIZWFkZXJzID0gewogICAgICAgICAgICAnUmVmZXJlcic6IHRoaXMuaG9zdCArICcvJywKICAgICAgICAgICAgJ1VzZXItQWdlbnQnOiB0aGlzLmhlYWRlcnNbJ1VzZXItQWdlbnQnXQogICAgICAgIH07CgogICAgICAgIGxldCBwbGF5VXJsID0gJyc7CiAgICAgICAgbGV0IGlzVmlkZW8gPSB1cmwuaW5jbHVkZXMoJ2Rvd24ucGhwJykgJiYgdXJsLmluY2x1ZGVzKCdhYz12cGxheScpOwogICAgICAgIGlmIChpc1ZpZGVvKSB7CiAgICAgICAgICAgIGxldCByZXMgPSBhd2FpdCByZXEodXJsLCB7IG1ldGhvZDogJ0dFVCcsIHJlZGlyZWN0OiAwLCBoZWFkZXJzOiBhcGlIZWFkZXJzIH0pOwogICAgICAgICAgICBwbGF5VXJsID0gcmVzLmhlYWRlcnMuTG9jYXRpb24gfHwgcmVzLmhlYWRlcnMubG9jYXRpb24gfHwgdGhpcy5fcHJvY2Vzc19hcGlfcmVzcG9uc2UocmVzKTsKICAgICAgICB9CgogICAgICAgIGVsc2UgaWYgKC9cLyhzb25nfG1wM3xyYWRpb3xyYWRpb2xpc3R8cmFkaW9wbGF5KVwvKFteL10rKVwuaHRtbC8udGVzdCh1cmwpKSB7CiAgICAgICAgICAgIGxldCBtaWQgPSB1cmwubWF0Y2goL1wvKFteL10rKVwuaHRtbC8pWzFdOwogICAgICAgICAgICBsZXQgcmVzID0gYXdhaXQgcmVxKHRoaXMuX2FicygnL2pzL3BsYXkucGhwJyksIHsKICAgICAgICAgICAgICAgIG1ldGhvZDogJ1BPU1QnLAogICAgICAgICAgICAgICAgaGVhZGVyczogewogICAgICAgICAgICAgICAgICAgIC4uLmFwaUhlYWRlcnMsCiAgICAgICAgICAgICAgICAgICAgJ1gtUmVxdWVzdGVkLVdpdGgnOiAnWE1MSHR0cFJlcXVlc3QnLAogICAgICAgICAgICAgICAgICAgICdDb250ZW50LVR5cGUnOiAnYXBwbGljYXRpb24veC13d3ctZm9ybS11cmxlbmNvZGVkJwogICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgIGRhdGE6IHsgaWQ6IG1pZCwgdHlwZTogJ211c2ljJyB9CiAgICAgICAgICAgIH0pOwogICAgICAgICAgICBwbGF5VXJsID0gdGhpcy5fcHJvY2Vzc19hcGlfcmVzcG9uc2UocmVzKTsKICAgICAgICB9CgogICAgICAgIGxldCBmaW5hbFVybCA9IHRoaXMuX2FicyhwbGF5VXJsIHx8IHVybCk7CiAgICAgICAgbGV0IHBsYXllckhlYWRlcnMgPSB7CiAgICAgICAgICAgICdVc2VyLUFnZW50JzogYXBpSGVhZGVyc1snVXNlci1BZ2VudCddLAogICAgICAgICAgICAuLi4oZmluYWxVcmwuaW5jbHVkZXMoJzIyYTUuY29tJykgJiYgeyAnUmVmZXJlcic6IGFwaUhlYWRlcnNbJ1JlZmVyZXInXSB9KQogICAgICAgIH07CgogICAgICAgIGlmIChpc1ZpZGVvKSB7CiAgICAgICAgICAgIHJldHVybiB7IHBhcnNlOiAwLCB1cmw6IGZpbmFsVXJsLCBoZWFkZXI6IHBsYXllckhlYWRlcnMgfTsKICAgICAgICB9CgogICAgICAgIGxldCBjb3ZlciA9ICcnOwogICAgICAgIHRyeSB7CiAgICAgICAgICAgIGxldCBwYWdlUmVzID0gYXdhaXQgcmVxKHVybCwgeyBoZWFkZXJzOiBhcGlIZWFkZXJzIH0pOwogICAgICAgICAgICBsZXQgcGFnZUh0bWwgPSB0eXBlb2YgcGFnZVJlcyA9PT0gJ29iamVjdCcgPyBwYWdlUmVzLmNvbnRlbnQgOiBwYWdlUmVzOwogICAgICAgICAgICBsZXQgb2dJbWFnZU1hdGNoID0gcGFnZUh0bWwubWF0Y2goLzxtZXRhW14+XStwcm9wZXJ0eT0ib2c6aW1hZ2UiW14+XStjb250ZW50PSIoW14iXSspIltePl0qPi9pKTsKICAgICAgICAgICAgaWYgKG9nSW1hZ2VNYXRjaCAmJiBvZ0ltYWdlTWF0Y2hbMV0pIHsKICAgICAgICAgICAgICAgIGNvdmVyID0gb2dJbWFnZU1hdGNoWzFdOwogICAgICAgICAgICB9CiAgICAgICAgfSBjYXRjaCB7fQoKICAgICAgICBsZXQgbHJjRGF0YSA9ICcnOwogICAgICAgIGlmIChscmNVcmwpIHsKICAgICAgICAgICAgdHJ5IHsKICAgICAgICAgICAgICAgIGxldCBscmNSZXMgPSBhd2FpdCByZXEobHJjVXJsLCB7IGhlYWRlcnM6IGFwaUhlYWRlcnMgfSk7CiAgICAgICAgICAgICAgICBsZXQgY29udGVudCA9IHR5cGVvZiBscmNSZXMgPT09ICdvYmplY3QnID8gbHJjUmVzLmNvbnRlbnQgOiBscmNSZXM7CiAgICAgICAgICAgICAgICBpZiAodHlwZW9mIGNvbnRlbnQgPT09ICdzdHJpbmcnICYmIGNvbnRlbnQuaW5jbHVkZXMoJ1snKSAmJiBjb250ZW50LmluY2x1ZGVzKCddJykpIHsKICAgICAgICAgICAgICAgICAgICBscmNEYXRhID0gY29udGVudDsKICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgfSBjYXRjaCB7fQogICAgICAgIH0KCiAgICAgICAgcmV0dXJuIHsKICAgICAgICAgICAgcGFyc2U6IDAsCiAgICAgICAgICAgIHVybDogZmluYWxVcmwsCiAgICAgICAgICAgIGhlYWRlcjogcGxheWVySGVhZGVycywKICAgICAgICAgICAgbHJjOiBscmNEYXRhLAogICAgICAgICAgICBwaWM6IGNvdmVyLAogICAgICAgICAgICBjb3ZlcjogY292ZXIsCiAgICAgICAgICAgIGhlaWdodDogNzIwCiAgICAgICAgfTsKICAgIH0sCgogICAgX3Byb2Nlc3NfYXBpX3Jlc3BvbnNlOiAocmVzKSA9PiB7CiAgICAgICAgaWYgKCFyZXMpIHJldHVybiAnJzsKICAgICAgICBjb25zdCBjb250ZW50ID0gdHlwZW9mIHJlcyA9PT0gJ29iamVjdCcgPyByZXMuY29udGVudCA6IHJlczsKICAgICAgICBjb25zdCB0cmltbWVkID0gY29udGVudC50cmltKCk7CiAgICAgICAgbGV0IGpzb247CiAgICAgICAgdHJ5IHsganNvbiA9IEpTT04ucGFyc2UoY29udGVudCk7IH0gY2F0Y2gge30gCiAgICAgICAgcmV0dXJuIGpzb24/LnVybCA/IGpzb24udXJsLnJlcGxhY2UoL1xcXC8vZywgJy8nKSA6ICh0cmltbWVkLnN0YXJ0c1dpdGgoJ2h0dHAnKSA/IHRyaW1tZWQgOiAnJyk7CiAgICB9Cn07 \ No newline at end of file diff --git "a/spider/js/\347\213\254\346\222\255\345\272\223[\344\274\230].js" "b/spider/js/\347\213\254\346\222\255\345\272\223[\344\274\230].js" new file mode 100644 index 00000000..ba2d1f75 --- /dev/null +++ "b/spider/js/\347\213\254\346\222\255\345\272\223[\344\274\230].js" @@ -0,0 +1,133 @@ +/* +@header({ + searchable: 1, + filterable: 1, + quickSearch: 0, + title: '独播库', + author: 'EylinSir', + '类型': '影视', + lang: 'ds' +}) +*/ + +var rule = { + 类型: '影视', + author: 'EylinSir', + title: '独播库', + host: 'https://api.dbokutv.com', + url: '/home', + searchUrl: '/vodsearch', + searchable: 1, + quickSearch: 0, + filterable: 1, + timeout: 10000, + play_parse: true, + headers: { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", + "Referer": "https://www.duboku.tv/" + }, + + get: async function(path) { + let url = this.getSignedUrl(path); + let resp = await _fetch(url, { headers: this.headers }); + return JSON.parse(await resp.text()); + }, + + format: function(list) { + return (list || []).map(j => ({ + title: j.Name, + url: `${this.host}${this.decodeData(j.DId || j.DuId)}`, + desc: j.Tag, + pic_url: this.decodeData(j.TnId), + vod_id: this.decodeData(j.DId || j.DuId) + })); + }, + + class_parse: async function() { + return { + class: [{ type_id: '2', type_name: '连续剧' }, { type_id: '1', type_name: '电影' }, { type_id: '3', type_name: '综艺' }, { type_id: '4', type_name: '动漫' }], + filters: {} + }; + }, + + 推荐: async function() { + let json = await this.get('/home'); + let videos = []; + (json || []).forEach(g => videos.push(...this.format(g.VodList))); + return setResult(videos); + }, + + 一级: async function(tid, pg) { + let page = (pg || 1).toString(); + let data = await this.get(`/vodshow/${tid}--------${page === '1' ? '' : page}---`); + return setResult(this.format(data.VodList)); + }, + + 二级: async function() { + let id = this.input.replace(this.host, ''); + let data = await this.get(id); + if (!data) return {}; + let playUrls = (data.Playlist || []).map(i => + `${i.EpisodeName}$${this.decodeData(i.VId)}` + ).join('#'); + return { + vod_id: id, + vod_name: data.Name, + vod_pic: this.decodeData(data.TnId), + vod_remarks: `评分:${data.Rating}`, + vod_year: data.ReleaseYear, + vod_area: data.Region, + vod_actor: Array.isArray(data.Actor) ? data.Actor.join(',') : data.Actor, + vod_director: data.Director, + vod_content: data.Description, + vod_play_from: '独播库', + vod_play_url: playUrls, + type_name: `${data.Genre || ''},${data.Scenario || ''}` + }; + }, + + 搜索: async function() { + let data = await this.get(`/vodsearch?wd=${this.KEY}`); + let url = this.getSignedUrl('/vodsearch') + `&wd=${this.KEY}`; + let resp = await _fetch(url, { headers: this.headers }); + return setResult(this.format(JSON.parse(await resp.text()))); + }, + + lazy: async function() { + let id = this.input.replace(this.host, ''); + let res = await this.get(id); + return { + parse: 0, + url: this.decodeData(res.HId), + header: { + 'User-Agent': this.headers['User-Agent'], + 'Origin': 'https://w.duboku.io', + 'Referer': 'https://w.duboku.io/' + } + }; + }, + + getSignedUrl: function(path) { + const ts = Math.floor(Date.now() / 1000).toString(); + const rand = Math.floor(Math.random() * 800000001); + const combined = (rand + 100000000).toString() + (900000000 - rand).toString(); + let interleaved = ''; + const len = Math.min(combined.length, ts.length); + for (let i = 0; i < len; i++) interleaved += combined[i] + ts[i]; + interleaved += combined.substring(len) + ts.substring(len); + const ssid = base64Encode(interleaved).replace(/=/g, '.'); + const rStr = (l) => Array(l).fill(0).map(() => 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'.charAt(Math.floor(Math.random() * 62))).join(''); + return `${this.host}${path}${path.includes('?') ? '&' : '?'}sign=${rStr(60)}&token=${rStr(38)}&ssid=${ssid}`; + }, + + decodeData: function(data) { + if (!data || typeof data !== 'string') return ''; + let str = data.replace(/['"]/g, '').trim(); + if (!str) return ''; + let res = ''; + for (let i = 0; i < str.length; i += 10) { + res += str.substring(i, i + 10).split('').reverse().join(''); + } + try { return base64Decode(res.replace(/\./g, '=')); } catch (e) { return ''; } + }, +}; \ No newline at end of file diff --git "a/spider/js/\347\223\234\345\255\220[\344\274\230].js" "b/spider/js/\347\223\234\345\255\220[\344\274\230].js" new file mode 100644 index 00000000..118a7540 --- /dev/null +++ "b/spider/js/\347\223\234\345\255\220[\344\274\230].js" @@ -0,0 +1,14 @@ +/* +@header({ + searchable: 2, + filterable: 1, + quickSearch: 0, + title: '瓜子', + author: 'EylinSir', + '类型': '影视', + logo: 'https://guaziyingshi.xxsnav.com/files/guaziyingshi.png', + lang: 'ds' +}) +*/ + +dmFyIHJ1bGUgPSB7CiAgICDnsbvlnos6ICflvbHop4YnLAogICAgYXV0aG9yOiAnRXlsaW5TaXInLAogICAgdGl0bGU6ICfnk5zlrZAnLAogICAgaG9zdDogJ2h0dHBzOi8vYXBpLnczMno3dnRkLmNvbScsCiAgICB1cmw6ICcvQXBwL0luZGV4TGlzdC9pbmRleExpc3QnLAogICAgc2VhcmNoVXJsOiAnL0FwcC9JbmRleC9maW5kTW9yZVZvZCcsCiAgICBsb2dvOiAnaHR0cHM6Ly9ndWF6aXlpbmdzaGkueHhzbmF2LmNvbS9maWxlcy9ndWF6aXlpbmdzaGkucG5nJywKICAgIHNlYXJjaGFibGU6IDIsCiAgICBxdWlja1NlYXJjaDogMCwKICAgIGZpbHRlcmFibGU6IDEsCiAgICBwbGF5X3BhcnNlOiB0cnVlLCAKICAgIGNsYXNzX25hbWU6ICfnlLXlvbEm55S16KeG5YmnJuWKqOa8qybnu7zoibom55+t5YmnJywKICAgIGNsYXNzX3VybDogJzEmMiY0JjMmNjQnLAogICAgaGVhZGVyczogewogICAgICAgICdVc2VyLUFnZW50JzogJ29raHR0cC8zLjEyLjAnLAogICAgICAgICdDb250ZW50LVR5cGUnOiAnYXBwbGljYXRpb24veC13d3ctZm9ybS11cmxlbmNvZGVkJywKICAgICAgICAnQ2FjaGUtQ29udHJvbCc6ICduby1jYWNoZScsCiAgICAgICAgJ1ZlcnNpb24nOiAnMjQwNjAyNScsCiAgICAgICAgJ1BhY2thZ2VOYW1lJzogJ2NvbS51ZjA3NmJmMGMyNDYucWU0MzlmMGQ1ZS5tOGFhZjU2YjcyNWEuaWZlYjY0NzM0NmYnLAogICAgICAgICdWZXInOiAnMS45LjInLAogICAgICAgICdSZWZlcmVyJzogJ2h0dHBzOi8vYXBpLnczMno3dnRkLmNvbScKICAgIH0sCiAgICB0b2tlbjogJzFiZTg2ZThlMThhOWZhMThiMmI4ZDU0MzI2OTlkYWQwLmFjMDA4ZWQ2NTBmZDA4N2JmYmVjZjJmZGE5ZDgyZTk4MzUyNTNlZjI0ODQzZTZiMThmY2QxMjhiMTA3NjM0OTdiY2Y5ZDUzZTk1OWY1Mzc3Y2RlMDM4YzIwY2NmOWQxN2Y2MDRjOWI4YmI2ZTYxMDQxZGVmODY3MjliMmZjNzQwOGJkMjQxZTIzYzIxM2FjNTdmMDIyNmVlNjU2ZTJiYjBhNTgzYWUwZTRmM2JmNmM2YWI2YzQ5MGM5YTZmMGQ4Y2RmZDM2NmFhY2Y1ZDgzMTkzNjcxYThmNzdjZDFhZjFmZjJlOTE0NWRlOTJlYzQzZWM4N2NmNGJkYzU2M2Y2ZTkxOWZlMzI4NjFiMGU5M2IxMThlYzM3ZDgwMzVmYmIzYy41OWRkMDVjNWQ5YThhZTcyNjUyODc4MzEyODIxOGYxNWZlNmYyYzBjODE0NWVkZGFiMTEyYjM3NGZjZmUzZDc5JywKICAgIHJzYVByaXZhdGVLZXk6IGAtLS0tLUJFR0lOIFBSSVZBVEUgS0VZLS0tLS0KTUlJQ2RnSUJBREFOQmdrcWhraUc5dzBCQVFFRkFBU0NBbUF3Z2dKY0FnRUFBb0dBZTZoS3JXTGkxelFtalRUMQpvemJFNFFkRmVKR054dWJ4bGQ2R3JGR3hpbXhmTXNNQjZCcEpocGNUb3VBcXl3QUZwcGlLZXRVQkJiWHdZc1lVCjF3TnI2NDhYVm1QbUNNQ3k0clk4dmRsaUZuYk1VajA4NkRVNlorL29YQmRXVTMvYjFHMEROM0U5d1VMUlN3Y0sKWlQzd2ovY0NJMXZzQ20zZ2oyUjVTcWtBOVkwQ0F3RUFBUUtCZ0FKSCs0Q3hWMC96QlZjTGlCQ0h2U0FObTBsNwpIZXR5YlRoL2oycDBZMXNUWHJvNEFMd0FhQ1RVZXFkQmpXaUxTbzlsTndESEZ5cTh6WDkwK2dOeGE3YzVFcWNXClY5Rm1sVlhyOFZoZkJ6Y1pvMW5YZU5kWEZUN3RRMnlhaC9vZHRkY3grdlJNU0dKZDF0LzVrNWJEZDl3QXZZZEkKRGJsTUFnK3dpS0taNUtjZEFrRUExY0Nha0VONE5leGtGNXRIUFJyUjZYT1kvWEhma3FYeEVoTXFtTmJCOVUzNApzYVRKbkxXSUhDOElYeXM2UW16ejMwVHR6Q2p1T3FLUlJ5K0ZNTTRUZHdKQkFKUVpGUGpzR0MrUnFjRzVVdlZNCmlNUGhud2UvYlhFZWhTaEs4NnlKSy9nL1VpS3JPODdoM2FFdTVnY0pxQnlnVHEzQkJCb0gybWQzcHIvVytoVU0KV0JzQ1FRQ2hmaFRJcmREaW5LaTZsUnhyZEJubjBPaGpnMmN3dXFLNXp6VTlwL04rUzl4N0NrOHdVSTUzREttOApqVUpFOFdBRzdXTGovb0NPV0VoK2ljNk5Jd1RkQWtFQWowWDhuaHg2QVhzZ0NZUnFsMWtsYnF0Vm1MOCs5NUtaCks3UG5MV0cvSWZqUVV5M3BQR29TYVo3ZmRxdUc4YnE4b3lmNStkempFL29UWGNCeVMrNlhSUUpBUC81Y2l5MWIKTDNOaFVoc2FPVnk1NU1IWG5QamRjVFgwRmFMaSt5YlhaSWZJUTJQNHJiMTltVnExZmVNYkNYaHorTDFyRzhvYQp0NWxZS2ZwZThrODNaQT09Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS1gLAoKICAgIENyeXB0b1V0aWxzOiB7CiAgICAgICAgYWVzRW5jcnlwdDogKHQsIGssIGkpID0+IENyeXB0b0pTLkFFUy5lbmNyeXB0KHQsIENyeXB0b0pTLmVuYy5VdGY4LnBhcnNlKGspLCB7IGl2OiBDcnlwdG9KUy5lbmMuVXRmOC5wYXJzZShpKSwgbW9kZTogQ3J5cHRvSlMubW9kZS5DQkMsIHBhZGRpbmc6IENyeXB0b0pTLnBhZC5Qa2NzNyB9KS5jaXBoZXJ0ZXh0LnRvU3RyaW5nKCkudG9VcHBlckNhc2UoKSwKICAgICAgICBhZXNEZWNyeXB0OiAodCwgaywgaSkgPT4gQ3J5cHRvSlMuQUVTLmRlY3J5cHQoeyBjaXBoZXJ0ZXh0OiBDcnlwdG9KUy5lbmMuSGV4LnBhcnNlKHQpIH0sIENyeXB0b0pTLmVuYy5VdGY4LnBhcnNlKGspLCB7IGl2OiBDcnlwdG9KUy5lbmMuVXRmOC5wYXJzZShpKSwgbW9kZTogQ3J5cHRvSlMubW9kZS5DQkMsIHBhZGRpbmc6IENyeXB0b0pTLnBhZC5Qa2NzNyB9KS50b1N0cmluZyhDcnlwdG9KUy5lbmMuVXRmOCksCiAgICAgICAgcnNhRGVjcnlwdDogKHQsIGspID0+IHsgdHJ5IHsgcmV0dXJuIGZvcmdlLnBraS5wcml2YXRlS2V5RnJvbVBlbShrKS5kZWNyeXB0KGZvcmdlLnV0aWwuZGVjb2RlNjQodCkpOyB9IGNhdGNoIChlKSB7IHJldHVybiAiIjsgfSB9CiAgICB9LAoKICAgIG1ha2VQeXRob25Kc29uOiBmdW5jdGlvbihkYXRhTWFwLCBvcmRlcikgewogICAgICAgIGxldCBwYXJ0cyA9IFtdLCBrZXlzID0gb3JkZXIgfHwgT2JqZWN0LmtleXMoZGF0YU1hcCk7CiAgICAgICAgbGV0IGVzYyA9IChzKSA9PiBzLnJlcGxhY2UoL1xcL2csICdcXFxcJykucmVwbGFjZSgvIi9nLCAnXFwiJykucmVwbGFjZSgvW1x1MDAwMC1cdTAwMWZcdTAwN2YtXHVmZmZmXS9nLCBjID0+ICdcXHUnICsgKCcwMDAwJyArIGMuY2hhckNvZGVBdCgwKS50b1N0cmluZygxNikpLnNsaWNlKC00KSk7CiAgICAgICAgZm9yIChsZXQgayBvZiBrZXlzKSBwYXJ0cy5wdXNoKGAiJHtrfSI6ICR7dHlwZW9mIGRhdGFNYXBba10gPT09ICdzdHJpbmcnID8gYCIke2VzYyhkYXRhTWFwW2tdKX0iYCA6IFN0cmluZyhkYXRhTWFwW2tdKX1gKTsKICAgICAgICByZXR1cm4gYHske3BhcnRzLmpvaW4oJywgJyl9fWA7CiAgICB9LAoKICAgIHBvc3REYXRhOiBhc3luYyBmdW5jdGlvbihhcGlQYXRoLCBkYXRhTWFwLCBrZXlPcmRlcikgewogICAgICAgIGxldCB0ID0gTWF0aC5mbG9vcihEYXRlLm5vdygpIC8gMTAwMCkudG9TdHJpbmcoKTsKICAgICAgICBsZXQgcmVxdWVzdEpzb24gPSB0aGlzLm1ha2VQeXRob25Kc29uKGRhdGFNYXAsIGtleU9yZGVyKTsKICAgICAgICBsZXQgcmVxdWVzdF9rZXkgPSB0aGlzLkNyeXB0b1V0aWxzLmFlc0VuY3J5cHQocmVxdWVzdEpzb24sICdtdlhCU1c3ZWtyZUl0TnNUJywgJzJVM0lySkw4c3pBS3AwRmonKTsKICAgICAgICBsZXQga2V5c19yYXcgPSAiUW14aTVjaVdYYlF6a3I3bytTVU5pVXVReFFFZjgvQVZ5VVdZNFQvQkdoY1hCSVV6NG5PeUhCR2Y5QTRLYk0waUtGM3lwOU03V0FZMHJyczVQemRUQU9CNDVwbGNTMnpaMHdVaWJjWHVHSjI5VlZHUldLR3dFOXp1MnZMd2hmZ2pUYWFEcFhvNHJieSs3R3hYVGt0ekpteHZuZU9VZFllSGkrUFpzVGhsdlBJPSI7CiAgICAgICAgbGV0IHNpZ25hdHVyZSA9IG1kNShgdG9rZW5faWQ9LHRva2VuPSR7dGhpcy50b2tlbn0scGhvbmVfdHlwZT0xLHJlcXVlc3Rfa2V5PSR7cmVxdWVzdF9rZXl9LGFwcF9pZD0xLHRpbWU9JHt0fSxrZXlzPSR7a2V5c19yYXd9KiZ6dmR2ZHZkZGJmaWtra3VtdG1kd3FwcHA/fDRZIXMhMmJyYCk7CiAgICAgICAgbGV0IHJlc3AgPSBhd2FpdCByZXEodGhpcy5ob3N0ICsgYXBpUGF0aCwgewogICAgICAgICAgICBtZXRob2Q6ICdwb3N0JywgaGVhZGVyczogdGhpcy5oZWFkZXJzLAogICAgICAgICAgICBkYXRhOiBbYHRva2VuPSR7dGhpcy50b2tlbn1gLCBgdG9rZW5faWQ9YCwgYHBob25lX3R5cGU9MWAsIGB0aW1lPSR7dH1gLCBgcGhvbmVfbW9kZWw9eGlhb21pLTIyMDIxMjExcmNgLCBga2V5cz0ke2VuY29kZVVSSUNvbXBvbmVudChrZXlzX3Jhdyl9YCwgYHJlcXVlc3Rfa2V5PSR7cmVxdWVzdF9rZXl9YCwgYHNpZ25hdHVyZT0ke3NpZ25hdHVyZX1gLCBgYXBwX2lkPTFgLCBgYWRfdmVyc2lvbj0xYF0uam9pbignJicpCiAgICAgICAgfSk7CiAgICAgICAgaWYgKCFyZXNwIHx8ICFyZXNwLmNvbnRlbnQpIHJldHVybiBudWxsOwogICAgICAgIHRyeSB7CiAgICAgICAgICAgIGxldCBqc29uID0gSlNPTi5wYXJzZShyZXNwLmNvbnRlbnQpOwogICAgICAgICAgICBpZiAoIWpzb24uZGF0YSkgcmV0dXJuIG51bGw7CiAgICAgICAgICAgIGxldCBib2R5a2kgPSBKU09OLnBhcnNlKHRoaXMuQ3J5cHRvVXRpbHMucnNhRGVjcnlwdChqc29uLmRhdGEua2V5cywgdGhpcy5yc2FQcml2YXRlS2V5KSk7CiAgICAgICAgICAgIGxldCBkZWNyeXB0ZWQgPSB0aGlzLkNyeXB0b1V0aWxzLmFlc0RlY3J5cHQoanNvbi5kYXRhLnJlc3BvbnNlX2tleSwgYm9keWtpLmtleSwgYm9keWtpLml2KTsKICAgICAgICAgICAgcmV0dXJuIChkZWNyeXB0ZWQgJiYgZGVjcnlwdGVkICE9PSAne30nKSA/IEpTT04ucGFyc2UoZGVjcnlwdGVkKSA6IG51bGw7CiAgICAgICAgfSBjYXRjaCAoZSkgeyByZXR1cm4gbnVsbDsgfQogICAgfSwKCiAgICDpooTlpITnkIY6IGFzeW5jIGZ1bmN0aW9uKCkgewogICAgICAgIGxldCBhcmVhID0gW3sibiI6ICLlhajpg6giLCAidiI6ICIwIn0sIHsibiI6ICLlpKfpmYYiLCAidiI6ICLlpKfpmYYifSwgeyJuIjogIummmea4ryIsICJ2IjogIummmea4ryJ9LCB7Im4iOiAi5Y+w5rm+IiwgInYiOiAi5Y+w5rm+In0sIHsibiI6ICLnvo7lm70iLCAidiI6ICLnvo7lm70ifSwgeyJuIjogIumfqeWbvSIsICJ2IjogIumfqeWbvSJ9LCB7Im4iOiAi5pel5pysIiwgInYiOiAi5pel5pysIn0sIHsibiI6ICLlhbbku5YiLCAidiI6ICLlhbbku5YifV07CiAgICAgICAgbGV0IHllYXIgPSBbeyJuIjogIuWFqOmDqCIsICJ2IjogIjAifV07CiAgICAgICAgZm9yIChsZXQgaSA9IDIwMjU7IGkgPj0gMjAwNTsgaS0tKSB5ZWFyLnB1c2goeyJuIjogU3RyaW5nKGkpLCAidiI6IFN0cmluZyhpKX0pOwogICAgICAgIGxldCBzb3J0ID0gW3sibiI6ICLmnIDmlrAiLCAidiI6ICJkX2lkIn0sIHsibiI6ICLmnIDng60iLCAidiI6ICJkX2hpdHMifSwgeyJuIjogIuaOqOiNkCIsICJ2IjogImRfc2NvcmUifV07CiAgICAgICAgbGV0IGZpbHRlcnMgPSB7fTsKICAgICAgICB0aGlzLmNsYXNzX3VybC5zcGxpdCgnJicpLmZvckVhY2godGlkID0+IHsKICAgICAgICAgICAgZmlsdGVyc1t0aWRdID0gW3sia2V5IjogImFyZWEiLCAibmFtZSI6ICLlnLDljLoiLCAidmFsdWUiOiBhcmVhfSwgeyJrZXkiOiAieWVhciIsICJuYW1lIjogIuW5tOS7vSIsICJ2YWx1ZSI6IHllYXJ9LCB7ImtleSI6ICJzb3J0IiwgIm5hbWUiOiAi5o6S5bqPIiwgInZhbHVlIjogc29ydH1dOwogICAgICAgIH0pOwogICAgICAgIHJ1bGUuZmlsdGVyID0gZmlsdGVyczsKICAgIH0sCgogICAg5o6o6I2QOiBhc3luYyBmdW5jdGlvbigpIHsgcmV0dXJuIFtdOyB9LAogICAgCiAgICDkuIDnuqc6IGFzeW5jIGZ1bmN0aW9uKHRpZCwgcGcsIGZpbHRlciwgZXh0ZW5kKSB7CiAgICAgICAgbGV0IGJvZHkgPSB7ICJhcmVhIjogU3RyaW5nKGV4dGVuZC5hcmVhIHx8ICcwJyksICJ5ZWFyIjogU3RyaW5nKGV4dGVuZC55ZWFyIHx8ICcwJyksICJwYWdlU2l6ZSI6ICIzMCIsICJzb3J0IjogU3RyaW5nKGV4dGVuZC5zb3J0IHx8ICdkX2lkJyksICJwYWdlIjogU3RyaW5nKHBnKSwgInRpZCI6IFN0cmluZyh0aWQpIH07CiAgICAgICAgbGV0IGRhdGEgPSBhd2FpdCB0aGlzLnBvc3REYXRhKCcvQXBwL0luZGV4TGlzdC9pbmRleExpc3QnLCBib2R5LCBbJ2FyZWEnLCAneWVhcicsICdwYWdlU2l6ZScsICdzb3J0JywgJ3BhZ2UnLCAndGlkJ10pOwogICAgICAgIHJldHVybiAoZGF0YSAmJiBkYXRhLmxpc3QpID8gZGF0YS5saXN0Lm1hcChpdGVtID0+ICh7CiAgICAgICAgICAgIHZvZF9pZDogaXRlbS52b2RfaWQgKyAnLycgKyAoaXRlbS52b2RfY29udGludSB8fCAwKSwKICAgICAgICAgICAgdm9kX25hbWU6IGl0ZW0udm9kX25hbWUsCiAgICAgICAgICAgIHZvZF9waWM6IGl0ZW0udm9kX3BpYywKICAgICAgICAgICAgdm9kX3JlbWFya3M6IGl0ZW0udm9kX2NvbnRpbnUgPT09IDAgPyAn55S15b2xJyA6IGDmm7TmlrDoh7Mke2l0ZW0udm9kX2NvbnRpbnV96ZuGYAogICAgICAgIH0pKSA6IFtdOwogICAgfSwKCiAgICDkuoznuqc6IGFzeW5jIGZ1bmN0aW9uKGlkcykgewogICAgICAgIGxldCB2b2RfaWQgPSBpZHNbMF0uc3BsaXQoJy8nKVswXSwgdCA9IE1hdGguZmxvb3IoRGF0ZS5ub3coKSAvIDEwMDApLnRvU3RyaW5nKCk7CiAgICAgICAgbGV0IHFkYXRhID0gYXdhaXQgdGhpcy5wb3N0RGF0YSgnL0FwcC9JbmRleFBsYXkvcGxheUluZm8nLCB7ICJ0b2tlbl9pZCI6ICIxNjQ5NDEyIiwgInZvZF9pZCI6IFN0cmluZyh2b2RfaWQpLCAibW9iaWxlX3RpbWUiOiB0LCAidG9rZW4iOiB0aGlzLnRva2VuIH0sIFsndG9rZW5faWQnLCAndm9kX2lkJywgJ21vYmlsZV90aW1lJywgJ3Rva2VuJ10pOwogICAgICAgIGxldCBqZGF0YSA9IGF3YWl0IHRoaXMucG9zdERhdGEoJy9BcHAvUmVzb3VyY2UvVnVybC9zaG93JywgeyAidnVybF9jbG91ZF9pZCI6ICIyIiwgInZvZF9kX2lkIjogU3RyaW5nKHZvZF9pZCkgfSwgWyd2dXJsX2Nsb3VkX2lkJywgJ3ZvZF9kX2lkJ10pOwogICAgICAgIGlmICghcWRhdGEgfHwgIXFkYXRhLnZvZEluZm8pIHJldHVybiB7fTsKICAgICAgICBsZXQgdm9kID0gcWRhdGEudm9kSW5mbzsKICAgICAgICBsZXQgcGxheUxpc3QgPSAoamRhdGEgJiYgamRhdGEubGlzdCkgPyBqZGF0YS5saXN0LmZpbHRlcihpID0+IGkucGxheSkubWFwKChpdGVtLCBpZHgpID0+IHsKICAgICAgICAgICAgbGV0IG4gPSBbXSwgcCA9IFtdOwogICAgICAgICAgICBmb3IgKGxldCBrIGluIGl0ZW0ucGxheSkgeyBpZiAoaXRlbS5wbGF5W2tdPy5wYXJhbSkgeyBuLnB1c2goayk7IHAucHVzaChpdGVtLnBsYXlba10ucGFyYW0pOyB9IH0KICAgICAgICAgICAgcmV0dXJuIHAubGVuZ3RoID4gMCA/IGAke2pkYXRhLmxpc3QubGVuZ3RoID09PSAxID8gdm9kLnZvZF9uYW1lIDogaWR4ICsgMX0kJHtwW3AubGVuZ3RoLTFdfXx8JHtuLmpvaW4oJ0AnKX1gIDogbnVsbDsKICAgICAgICB9KS5maWx0ZXIoQm9vbGVhbikgOiBbXTsKCiAgICAgICAgcmV0dXJuIHsKICAgICAgICAgICAgdm9kX2lkOiB2b2RfaWQsIAogICAgICAgICAgICB2b2RfbmFtZTogdm9kLnZvZF9uYW1lLCAKICAgICAgICAgICAgdm9kX3BpYzogdm9kLnZvZF9waWMsIAogICAgICAgICAgICB0eXBlX25hbWU6IHZvZC52b2RfY2xhc3MgfHwgJycsCiAgICAgICAgICAgIHZvZF95ZWFyOiB2b2Qudm9kX3llYXIsIAogICAgICAgICAgICB2b2RfYXJlYTogdm9kLnZvZF9hcmVhLCAKICAgICAgICAgICAgdm9kX2FjdG9yOiB2b2Qudm9kX2FjdG9yLCAKICAgICAgICAgICAgdm9kX2RpcmVjdG9yOiB2b2Qudm9kX2RpcmVjdG9yLAogICAgICAgICAgICB2b2RfY29udGVudDogKHZvZC52b2RfdXNlX2NvbnRlbnQgfHwgJycpLnRyaW0oKSwgCiAgICAgICAgICAgIHZvZF9wbGF5X2Zyb206ICfll5Hnk5zlrZAnLCAKICAgICAgICAgICAgdm9kX3BsYXlfdXJsOiBwbGF5TGlzdC5qb2luKCcjJykKICAgICAgICB9OwogICAgfSwKCiAgICDmkJzntKI6IGFzeW5jIGZ1bmN0aW9uKGtleSwgcXVpY2ssIHBnKSB7CiAgICAgICAgbGV0IGRhdGEgPSBhd2FpdCB0aGlzLnBvc3REYXRhKCcvQXBwL0luZGV4L2ZpbmRNb3JlVm9kJywgeyAia2V5d29yZHMiOiBrZXksICJvcmRlcl92YWwiOiAiMSIsICJwYWdlIjogU3RyaW5nKHBnIHx8IDEpIH0sIFsna2V5d29yZHMnLCAnb3JkZXJfdmFsJywgJ3BhZ2UnXSk7CiAgICAgICAgcmV0dXJuIChkYXRhICYmIGRhdGEubGlzdCkgPyBkYXRhLmxpc3QubWFwKGl0ZW0gPT4gKHsKICAgICAgICAgICAgdm9kX2lkOiBpdGVtLnZvZF9pZCArICcvJyArIChpdGVtLnZvZF9jb250aW51IHx8IDApLAogICAgICAgICAgICB2b2RfbmFtZTogaXRlbS52b2RfbmFtZSwKICAgICAgICAgICAgdm9kX3BpYzogaXRlbS52b2RfcGljLAogICAgICAgICAgICB2b2RfcmVtYXJrczogaXRlbS52b2RfY29udGludSA9PT0gMCA/ICfnlLXlvbEnIDogYOabtOaWsOiHsyR7aXRlbS52b2RfY29udGludX3pm4ZgCiAgICAgICAgfSkpIDogW107CiAgICB9LAoKICAgIGxhenk6IGFzeW5jIGZ1bmN0aW9uKGZsYWcsIGlkLCBmbGFncykgewogICAgICAgIHRyeSB7CiAgICAgICAgICAgIGxldCBwYXJ0cyA9IGlkLnNwbGl0KCd8fCcpOwogICAgICAgICAgICBpZiAocGFydHMubGVuZ3RoIDwgMikgcmV0dXJuIHsgcGFyc2U6IDAsIHVybDogJycgfTsKICAgICAgICAgICAgbGV0IHBhcmFtcyA9IHt9LCBrZXlzID0gW107CiAgICAgICAgICAgIHBhcnRzWzBdLnNwbGl0KCcmJykuZm9yRWFjaChwYWlyID0+IHsKICAgICAgICAgICAgICAgIGxldCBbaywgdl0gPSBwYWlyLnNwbGl0KCc9Jyk7CiAgICAgICAgICAgICAgICBpZiAoaykgeyBwYXJhbXNba10gPSB2OyBpZiAoIWtleXMuaW5jbHVkZXMoaykpIGtleXMucHVzaChrKTsgfQogICAgICAgICAgICB9KTsKICAgICAgICAgICAgbGV0IHJlcyA9IChwYXJ0c1sxXSB8fCAnJykuc3BsaXQoJ0AnKS5zb3J0KChhLCBiKSA9PiAocGFyc2VJbnQoYi5yZXBsYWNlKC9cRC9nLCAnJykpIHx8IDApIC0gKHBhcnNlSW50KGEucmVwbGFjZSgvXEQvZywgJycpKSB8fCAwKSk7CiAgICAgICAgICAgIGlmIChyZXMubGVuZ3RoID4gMCkgewogICAgICAgICAgICAgICAgcGFyYW1zWydyZXNvbHV0aW9uJ10gPSByZXNbMF07CiAgICAgICAgICAgICAgICBpZiAoIWtleXMuaW5jbHVkZXMoJ3Jlc29sdXRpb24nKSkga2V5cy5wdXNoKCdyZXNvbHV0aW9uJyk7CiAgICAgICAgICAgICAgICBsZXQgZGF0YSA9IGF3YWl0IHRoaXMucG9zdERhdGEoJy9BcHAvUmVzb3VyY2UvVnVybERldGFpbC9zaG93T25lJywgcGFyYW1zLCBrZXlzKTsKICAgICAgICAgICAgICAgIGlmIChkYXRhICYmIChkYXRhLnVybCB8fCBkYXRhLm0zdTggfHwgZGF0YS52dXJsKSkgcmV0dXJuIHsgcGFyc2U6IDAsIHVybDogZGF0YS51cmwgfHwgZGF0YS5tM3U4IHx8IGRhdGEudnVybCwgaGVhZGVyOiB0aGlzLmhlYWRlcnMgfTsKICAgICAgICAgICAgfQogICAgICAgIH0gY2F0Y2ggKGUpIHt9CiAgICAgICAgcmV0dXJuIHsgcGFyc2U6IDAsIHVybDogJycgfTsKICAgIH0KfTs= diff --git "a/spider/js/\347\224\265\345\275\261\346\270\257[\347\243\201].js" "b/spider/js/\347\224\265\345\275\261\346\270\257[\347\243\201].js" new file mode 100644 index 00000000..59af8043 --- /dev/null +++ "b/spider/js/\347\224\265\345\275\261\346\270\257[\347\243\201].js" @@ -0,0 +1,14 @@ +/* +@header({ + searchable: 1, + filterable: 0, + quickSearch: 0, + title: '电影港', + author: 'EylinSir', + '类型': '影视', + logo: 'https://www.dyg123.net/favicon.ico', + lang: 'ds' +}) +*/ + +dmFyIHJ1bGUgPSB7CiAgICDnsbvlnos6ICflvbHop4YnLAogICAgYXV0aG9yOiAnRXlsaW5TaXInLAogICAgdGl0bGU6ICfnlLXlvbHmuK8nLAogICAgaG9zdDogJ2h0dHBzOi8vd3d3LmR5ZzEyMy5uZXQnLAogICAgaG9tZVVybDogJy8nLAogICAgdXJsOiAnL2UvYWN0aW9uL0xpc3RJbmZvLnBocD9meWZpbHRlcicsCiAgICBsb2dvOiAnaHR0cHM6Ly93d3cuZHlnMTIzLm5ldC9mYXZpY29uLmljbycsCiAgICBmaWx0ZXJfdXJsOiAnY2xhc3NpZD17e2ZsLmNhdGVJZCBvciAiZnljbGFzcyJ9fSZwYWdlPShmeXBhZ2UtMSkmbGluZT0zMCZ0ZW1waWQ9MSZvcmRlcmJ5PXt7ZmwuYnkgb3IgIm5ld3N0aW1lIn19JywKICAgIHNlYXJjaFVybDogJy9lL3NlYXJjaC9pbmRleC5waHAnLAogICAgZGV0YWlsVXJsOiAnJywKICAgIHNlYXJjaGFibGU6IDEsIAogICAgcXVpY2tTZWFyY2g6IDAsIAogICAgZmlsdGVyYWJsZTogMCwgCiAgICB0aW1lb3V0OiA1MDAwLAogICAgbGltaXQ6IDIwLAogICAgaGVhZGVyczogeydVc2VyLUFnZW50JzogTU9CSUxFX1VBfSwKICAgIGNsYXNzX25hbWU6ICfnlLXlvbEm5Ymn6ZuGJue7vOiJuibliqjnlLsm55+t5YmnJywKICAgIGNsYXNzX3VybDogJzEmMjAmMzEmMzAmMzInLAogICAgZmlsdGVyOiAnSDRzSUFBQUFBQUFBQTZ2bVVnQUNKVU1scTJnd0N3U3FsYkpUSzVXc2xKSVRTMUk5VTVSMGxQSVNjMU9CL09jYmR6K2QxdzNrbHlYbWxBSUZvcXVWOG9EQ1QxdFh2R3hlQVJJR2NneVZhbldnd2wwcm51eWQ4N3l6SFNwamhKQ1pOdWRwNTNLRWpERmM1bm5IeG1mTnJRZ1pFNFRNOG9sUGQrNUd5SmdpVE90Y2pxTEhEQzd6ckhIQ3M0WnBDQmx6aEV6SGpDZTdPaEV5aGdpcDU3dFdQZDA3RlVuS1FxazJ0bFlISTNDU0toRUI4Nnh2MHROZC9SZ0I4MnhPdzdOcEc2RG01S1dXRjVka0FwWERMSHF5YTllekRWT2dzdmw1eVRtWnlka2dxOEEyeFVJc1ZESXlvRmJFQUUyQ2g5anN2Y0JBZzRrall1elo5S1V2NTY5RWtrSkV6TE0xeTUvdjYwT1NNaG5JVURIR21senBaRGZXR0tHVDNVWURZRGRYTFFBTWhxdkhKQVFBQUE9PScsCiAgICDmjqjojZA6ICcqJywKICAgIAogICAg5LiA57qnOiBhc3luYyBmdW5jdGlvbiAoKSB7CiAgICAgICAgcmV0dXJuIHJ1bGUuZ2V0Vm9kTGlzdChhd2FpdCBmZXRjaCh0aGlzLmlucHV0KSk7CiAgICB9LAoKICAgIOaQnOe0ojogYXN5bmMgZnVuY3Rpb24gKCkgewogICAgICAgIGxldCB7aW5wdXQsIEtFWSwgSE9TVH0gPSB0aGlzOwogICAgICAgIGxldCBWT0RTID0gW107CiAgICAgICAgbGV0IGh0bWwgPSBhd2FpdCBmZXRjaChpbnB1dCwgewogICAgICAgICAgICBoZWFkZXJzOiB7Li4ucnVsZS5oZWFkZXJzLCAiQ29udGVudC1UeXBlIjogImFwcGxpY2F0aW9uL3gtd3d3LWZvcm0tdXJsZW5jb2RlZDsgY2hhcnNldD11dGYtOCJ9LAogICAgICAgICAgICBtZXRob2Q6ICdQT1NUJywKICAgICAgICAgICAgYm9keTogYGtleWJvYXJkPSR7S0VZfSZzdWJtaXQ95pCc57SiJnNob3c9dGl0bGUmdGVtcGlkPTFgCiAgICAgICAgfSk7CiAgICAgICAgVk9EUy5wdXNoKC4uLnJ1bGUuZ2V0Vm9kTGlzdChodG1sKSk7CiAgICAgICAgbGV0IHNlYXJjaElkID0gcnVsZS5jdXRTdHIoaHRtbCwgJ3NlYXJjaGlkPScsICciJywgJycpOwogICAgICAgIGlmIChzZWFyY2hJZCkgewogICAgICAgICAgICBsZXQgbmV4dEh0bWwgPSBhd2FpdCBmZXRjaChgJHtIT1NUIHx8IHJ1bGUuaG9zdH0vZS9zZWFyY2gvcmVzdWx0L2luZGV4LnBocD9wYWdlPTEmc2VhcmNoaWQ9JHtzZWFyY2hJZH1gKTsKICAgICAgICAgICAgVk9EUy5wdXNoKC4uLnJ1bGUuZ2V0Vm9kTGlzdChuZXh0SHRtbCkpOwogICAgICAgIH0KICAgICAgICByZXR1cm4gVk9EUzsKICAgIH0sCgogICAg5LqM57qnOiBhc3luYyBmdW5jdGlvbiAoKSB7CiAgICAgICAgbGV0IHtpbnB1dCwgSE9TVH0gPSB0aGlzOwogICAgICAgIGxldCBbaWQsIGtuYW1lLCBrcGljLCBrcmVtYXJrc10gPSBpbnB1dC5zcGxpdCgnQCcpOwogICAgICAgIGxldCBodG1sID0gYXdhaXQgZmV0Y2goaWQpOwogICAgICAgIGNvbnN0IGNsZWFuID0gKHMpID0+IHsKICAgICAgICAgICAgaWYgKCFzKSByZXR1cm4gJyc7CiAgICAgICAgICAgIHRyeSB7IHMgPSBkZWNvZGVVUklDb21wb25lbnQocyk7IH0gY2F0Y2ggKGUpIHt9CiAgICAgICAgICAgIHJldHVybiBzLnJlcGxhY2UoLzxbXj5dKz58Jm5ic3A7fFxzK3zmiYvmnLrniYh8LeWcqOe6v+WFjei0ueingueciy1855S15b2x5rivKD86XChEU1wpKT8vZywgJycpLnRyaW0oKTsKICAgICAgICB9OwogICAgICAgIGxldCBrZGV0YWlsID0gcGRmaChodG1sLCAnLmN0LWwnKS5zcGxpdCgnPHN0cm9uZz4nKVswXTsKICAgICAgICBsZXQgZmluYWxOYW1lID0gY2xlYW4ocGRmaChodG1sLCAndGl0bGUnKSkgfHwgY2xlYW4oa25hbWUpOwogICAgICAgIGxldCB0YWJzID0gWwogICAgICAgICAgICAuLi4ocGRmYShodG1sLCAnc3Ryb25nOmhhcyhzcGFuKScpLm1hcCgoaXQsIGkpID0+IHJ1bGUuY3V0U3RyKGl0LCAn44CQJywgJ+OAkScsIGDno4Hlipvnur8ke2kgKyAxfWApKSksCiAgICAgICAgICAgIC4uLihwZGZhKGh0bWwsICcjdGFiODEnKS5tYXAoaXQgPT4gcGRmaChpdCwgJ2JvZHkmJlRleHQnKSkpCiAgICAgICAgXTsKICAgICAgICBsZXQgdXJscyA9IFsKICAgICAgICAgICAgLi4uKHBkZmEoaHRtbCwgJ3Rib2R5JykubWFwKGl0ZW0gPT4gCiAgICAgICAgICAgICAgICBwZGZhKGl0ZW0sICdhJykubWFwKGl0ID0+IHBkZmgoaXQsICdib2R5JiZUZXh0JykgKyAnJCcgKyBwZGZoKGl0LCAnYSYmaHJlZicpKS5qb2luKCcjJykKICAgICAgICAgICAgKSksCiAgICAgICAgICAgIC4uLihwZGZhKGh0bWwsICcudmlkZW91cmwnKS5tYXAoaXRlbSA9PiAKICAgICAgICAgICAgICAgIHBkZmEoaXRlbSwgJ2EnKS5tYXAoaXQgPT4gcGRmaChpdCwgJ2JvZHkmJlRleHQnKSArICckJyArIHBkKGl0LCAnYSYmaHJlZicsIEhPU1QgfHwgcnVsZS5ob3N0KSkuam9pbignIycpCiAgICAgICAgICAgICkpCiAgICAgICAgXTsKCiAgICAgICAgcmV0dXJuIHsKICAgICAgICAgICAgdm9kX2lkOiBpZCwKICAgICAgICAgICAgdm9kX25hbWU6IGZpbmFsTmFtZSwKICAgICAgICAgICAgdm9kX3BpYzoga3BpYywKICAgICAgICAgICAgdHlwZV9uYW1lOiBydWxlLmN1dFN0cihrZGV0YWlsLCAn4peO57G75YirJywgJ+KXjicsICfnsbvlnosnKSwKICAgICAgICAgICAgdm9kX3JlbWFya3M6IGNsZWFuKGtyZW1hcmtzKSwKICAgICAgICAgICAgdm9kX3llYXI6IHJ1bGUuY3V0U3RyKGtkZXRhaWwsICfil47lubTku6MnLCAn4peOJywgJzEwMDAnKSwKICAgICAgICAgICAgdm9kX2FyZWE6IHJ1bGUuY3V0U3RyKGtkZXRhaWwsICfil47kuqflnLAnLCAn4peOJywgJ+WcsOWMuicpLAogICAgICAgICAgICB2b2RfbGFuZzogcnVsZS5jdXRTdHIoa2RldGFpbCwgJ+KXjuivreiogCcsICfil44nLCAn6K+t6KiAJyksCiAgICAgICAgICAgIHZvZF9kaXJlY3RvcjogcnVsZS5jdXRTdHIoa2RldGFpbCwgJ+KXjuWvvOa8lCcsICfil44nLCAn5a+85ryUJyksCiAgICAgICAgICAgIHZvZF9hY3RvcjogcnVsZS5jdXRTdHIoa2RldGFpbCwgJ+KXjua8lOWRmCcsICc8L3A+JywgJycpIHx8IHJ1bGUuY3V0U3RyKGtkZXRhaWwsICfil47kuLvmvJQnLCAnPC9wPicsICfkuLvmvJQnKSwKICAgICAgICAgICAgdm9kX2NvbnRlbnQ6IGNsZWFuKHJ1bGUuY3V0U3RyKGtkZXRhaWwsICfil47nroDku4vCoz4nLCAnPC9wPicsICcnKSkgfHwgZmluYWxOYW1lLAogICAgICAgICAgICB2b2RfcGxheV9mcm9tOiB0YWJzLmpvaW4oJyQkJCcpLAogICAgICAgICAgICB2b2RfcGxheV91cmw6IHVybHMuam9pbignJCQkJykKICAgICAgICB9OwogICAgfSwKCiAgICBwbGF5X3BhcnNlOiB0cnVlLAogICAgbGF6eTogYXN5bmMgZnVuY3Rpb24gKCkgewogICAgICAgIGxldCB7aW5wdXR9ID0gdGhpczsKICAgICAgICBpZiAoL15tYWduZXQvLnRlc3QoaW5wdXQpKSByZXR1cm4geyBqeDogMCwgcGFyc2U6IDAsIHVybDogaW5wdXQgfTsKICAgICAgICBsZXQgdXJsID0gaW5wdXQ7CiAgICAgICAgdHJ5IHsKICAgICAgICAgICAgbGV0IGh0bWwgPSBhd2FpdCBmZXRjaChpbnB1dCk7CiAgICAgICAgICAgIGxldCByZWFsVXJsID0gcnVsZS5jdXRTdHIoaHRtbCwgImE6JyIsICInIiwgJycpOwogICAgICAgICAgICBpZiAoIS9tM3U4fG1wNHxta3YvLnRlc3QocmVhbFVybCkpIHsKICAgICAgICAgICAgICAgIGxldCBpZnJhbWVTcmMgPSBydWxlLmN1dFN0cihodG1sLCAnPGlmcmFtZcKjc3JjPSInLCAnIicsICcnKTsKICAgICAgICAgICAgICAgIGlmIChpZnJhbWVTcmMpIHsKICAgICAgICAgICAgICAgICAgICBsZXQgaWZyYW1lSHRtbCA9IGF3YWl0IGZldGNoKGlmcmFtZVNyYyk7CiAgICAgICAgICAgICAgICAgICAgcmVhbFVybCA9IGdldEhvbWUoaWZyYW1lU3JjKSArIHJ1bGUuY3V0U3RyKGlmcmFtZUh0bWwsICd1cmwgPSAiJywgJyInLCAnJyk7CiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgIH0KICAgICAgICAgICAgaWYgKC9tM3U4fG1wNHxta3YvLnRlc3QocmVhbFVybCkpIHVybCA9IHJlYWxVcmw7CiAgICAgICAgfSBjYXRjaCAoZSkge30KICAgICAgICAKICAgICAgICByZXR1cm4geyBqeDogMCwgcGFyc2U6IDEsIHVybDogdXJsLCBoZWFkZXI6IHJ1bGUuaGVhZGVycyB9OwogICAgfSwKCiAgICBnZXRWb2RMaXN0OiBmdW5jdGlvbihodG1sKSB7CiAgICAgICAgbGV0IGxpc3QgPSBwZGZhKGh0bWwsICcubTEnKSB8fCBbXTsKICAgICAgICByZXR1cm4gbGlzdC5tYXAoaXQgPT4gewogICAgICAgICAgICBsZXQgbmFtZSA9IHJ1bGUuY3V0U3RyKGl0LCAnYWx0PSInLCAnIicsICflkI3np7AnKTsKICAgICAgICAgICAgbGV0IHBpYyA9IHJ1bGUuY3V0U3RyKGl0LCAnZGF0YS1vcmlnaW5hbD0iJywgJyInLCAn5Zu+54mHJyk7CiAgICAgICAgICAgIGxldCByZW1hcmsgPSBydWxlLmN1dFN0cihpdCwgJ290aGVyIj4nLCAnPC9wPicsICfnirbmgIEnKTsKICAgICAgICAgICAgdHJ5IHsgcmVtYXJrID0gZGVjb2RlVVJJQ29tcG9uZW50KHJlbWFyaykucmVwbGFjZSgvPFtePl0rPnwmbmJzcDsvZywgJycpLnRyaW0oKTsgfSBjYXRjaChlKSB7fQogICAgICAgICAgICByZXR1cm4gewogICAgICAgICAgICAgICAgdm9kX25hbWU6IG5hbWUsCiAgICAgICAgICAgICAgICB2b2RfcGljOiBwaWMsCiAgICAgICAgICAgICAgICB2b2RfcmVtYXJrczogcmVtYXJrLAogICAgICAgICAgICAgICAgdm9kX2lkOiBgJHtydWxlLmN1dFN0cihpdCwgJ2hyZWY9IicsICciJywgJ0lkJyl9QCR7bmFtZX1AJHtwaWN9QCR7cmVtYXJrfWAKICAgICAgICAgICAgfTsKICAgICAgICB9KTsKICAgIH0sCgogICAgY3V0U3RyOiBmdW5jdGlvbihzdHIsIHByZSwgc3VmLCBkZWYgPSAnJykgewogICAgICAgIHRyeSB7CiAgICAgICAgICAgIGlmICghc3RyKSByZXR1cm4gZGVmOwogICAgICAgICAgICBsZXQgZXNjID0gcyA9PiBzLnJlcGxhY2UoL1suKis/JHt9KCl8W1xdXFwvXl0vZywgJ1xcJCYnKS5yZXBsYWNlKC/Coy9nLCAnW15dKj8nKTsKICAgICAgICAgICAgbGV0IHJlZyA9IG5ldyBSZWdFeHAoYCR7ZXNjKHByZSl9KFteXSo/KSR7ZXNjKHN1Zil9YCk7CiAgICAgICAgICAgIGxldCByZXMgPSBzdHIubWF0Y2gocmVnKT8uWzFdID8/IGRlZjsKICAgICAgICAgICAgcmV0dXJuIHJlcy5yZXBsYWNlKC88W14+XSs+fCZuYnNwO3xccysvZywgJyAnKS50cmltKCk7CiAgICAgICAgfSBjYXRjaCB7IHJldHVybiBkZWY7IH0KICAgIH0KfTs= \ No newline at end of file diff --git "a/spider/js/\347\225\252\350\214\204\345\260\217\350\257\264[\344\271\246].js" "b/spider/js/\347\225\252\350\214\204\345\260\217\350\257\264[\344\271\246].js" index 7a5a2929..2450618a 100644 --- "a/spider/js/\347\225\252\350\214\204\345\260\217\350\257\264[\344\271\246].js" +++ "b/spider/js/\347\225\252\350\214\204\345\260\217\350\257\264[\344\271\246].js" @@ -6,6 +6,7 @@ title: '番茄小说[书]', author: '道长', '类型': '小说', + logo: 'https://www.18zf.net/d/file/p/2023/1107/3ty5orktxrc.jpg', lang: 'ds' }) */ @@ -23,7 +24,8 @@ const {getRandomFromList} = $.require('./_lib.random.js'); const {requestHtml} = $.require('./_lib.request.js'); // const fqweb_host = 'http://fqweb.jsj66.com'; // const fqweb_host = 'http://fanqie.mduge.com'; -const fqweb_host = 'https://qkfqapi.vv9v.cn'; +// const fqweb_host = 'https://qkfqapi.vv9v.cn'; +const fqweb_host = 'http://101.35.133.34:5000'; // const fqweb_host = 'http://101.35.133.34:5000/docs'; //备选 // const fqweb_host = 'http://103.236.91.147:9999/docs'; //备选 // const fqweb_host = 'http://47.108.80.161:5005/docs'; //备选 @@ -36,6 +38,7 @@ var rule = { title: '番茄小说[书]', desc: '番茄小说纯js版本', host: 'https://fanqienovel.com/', + logo: 'https://www.18zf.net/d/file/p/2023/1107/3ty5orktxrc.jpg', homeUrl: 'https://fanqienovel.com/api/author/book/category_list/v0/', url: '/api/author/library/book_list/v0/?page_count=18&page_index=(fypage-1)&gender=1&category_id=fyclass&creation_status=-1&word_count=-1&book_type=-1&sort=0#fyfilter', // searchUrl: fqweb_host + '/search?query=**&page=fypage', @@ -60,7 +63,7 @@ var rule = { api: 'https://novel.snssdk.com/api', 封面域名: 'http://p6-novel.byteimg.com/large/', }, - timeout: 5000, + timeout: 20000, play_parse: true, class_parse: async () => { // let html = (await req(rule.homeUrl)).content; @@ -206,7 +209,8 @@ var rule = { content = content.replace(/<\/p>/g, '\n').replace(/<\w+>/g, '').replace(/<[^>]*>/g, ''); */ - let html = (await req(content_url, {headers: {Cookie: getFqCookie()}})).content; + // let html = (await req(content_url, {headers: {Cookie: getFqCookie()},timeout:this.timeout})).content; + let html = await request(content_url); /* let json = JSON.parse(html).data.data; title = json.novel_data.title; diff --git "a/spider/js/\347\225\252\350\214\204\346\274\253\347\224\273[\347\224\273].js" "b/spider/js/\347\225\252\350\214\204\346\274\253\347\224\273[\347\224\273].js" index 04519808..98e71dda 100644 --- "a/spider/js/\347\225\252\350\214\204\346\274\253\347\224\273[\347\224\273].js" +++ "b/spider/js/\347\225\252\350\214\204\346\274\253\347\224\273[\347\224\273].js" @@ -5,18 +5,22 @@ quickSearch: 0, title: '番茄漫画', '类型': '漫画', + logo: 'https://www.18zf.net/d/file/p/2023/1107/3ty5orktxrc.jpg', lang: 'ds' }) */ + var rule = { 类型: '漫画', title: '番茄漫画', - host: 'https://qkfqapi.vv9v.cn', + // host: 'https://qkfqapi.vv9v.cn', + host: 'http://47.108.80.161:5005', homeUrl: '/api/discover/style?tab=漫画', url: 'fyclass', searchUrl: '/api/search?key=**&tab_type=8&offset=((fypage-1)*10)', detailUrl: '/api/detail?book_id=fyid', + logo: 'https://www.18zf.net/d/file/p/2023/1107/3ty5orktxrc.jpg', headers: {'User-Agent': 'UC_UA'}, searchable: 1, quickSearch: 0, @@ -26,21 +30,22 @@ var rule = { limit: 10, class_parse: async function () { let {input} = this; + // log('[class_parse] input:', input); let html = await request(input); let data = html.parseX.data; let d = data.filter(item => item.url.trim()).map((it) => { return { type_name: it.title, - type_id: it.url, + type_id: gzip(it.url), } }); return {class: d} }, lazy: async function () { - let {input, pdfa, pdfh} = this; + let {input, pdfa, pdfh, HOST} = this; let title = input.split('@')[1]; input = input.split('@')[0]; - let content_url = `https://qkfqapi.vv9v.cn/api/content?tab=漫画&item_id=${input}&show_html=0`; // 正文获取接口 + let content_url = `${HOST}/api/content?tab=漫画&item_id=${input}&show_html=0`; // 正文获取接口 let jsonStr = await request(content_url); let images = jsonStr.parseX.data.images; images = pdfa(images, 'img'); @@ -71,16 +76,20 @@ var rule = { 推荐: async function () { let {HOST} = this; let url = HOST + '/api/discover?tab=漫画&type=7&gender=2&genre_type=110&page=1'; + // log('[推荐]: url: ' + url); let html = await request(url); return this.parseList(html); }, 一级: async function (tid, pg, filter, extend) { + // log('[一级]: tid:', tid); + tid = ungzip(tid); input = jinja.render(tid, {page: pg}); + // log('[一级]: input: ' + input); let html = await request(input); return this.parseList(html); }, 二级: async function () { - let {input, orId} = this; + let {input, orId, HOST} = this; let html = await request(input); let data = html.parseX.data.data; let VOD = {}; @@ -94,7 +103,7 @@ var rule = { VOD.vod_actor = ''; VOD.vod_director = data.author; VOD.vod_play_from = '番茄漫画'; - let jsonStr = await request(`https://qkfqapi.vv9v.cn/api/book?book_id=${orId}`); + let jsonStr = await request(`${HOST}/api/book?book_id=${orId}`); let book_info = jsonStr.parseX.data.data; let list = book_info.chapterListWithVolume.flat(); let urls = []; diff --git "a/spider/js/\347\225\252\350\214\204\347\225\205\345\220\254[\345\220\254].js" "b/spider/js/\347\225\252\350\214\204\347\225\205\345\220\254[\345\220\254].js" index d55334d0..9411cfbc 100644 --- "a/spider/js/\347\225\252\350\214\204\347\225\205\345\220\254[\345\220\254].js" +++ "b/spider/js/\347\225\252\350\214\204\347\225\205\345\220\254[\345\220\254].js" @@ -5,9 +5,10 @@ quickSearch: 0, title: '番茄畅听', author: 'EylinSir', + logo: 'https://www.18zf.net/d/file/p/2023/1107/3ty5orktxrc.jpg', '类型': '听书', lang: 'ds' }) */ -dmFyIHJ1bGUgPSB7CiAgICDnsbvlnos6ICflkKzkuaYnLAogICAgYXV0aG9yOiAnRXlsaW5TaXInLAogICAgdGl0bGU6ICfnlarojITnlYXlkKwnLAogICAgaG9zdDogJ2h0dHBzOi8vcWtmcWFwaS52djl2LmNuJywKICAgIHVybDogJy9hcGkvZGlzY292ZXI/dGFiPeWQrOS5piZ0eXBlPWZ5Y2xhc3MmZ2VuZGVyPTImZ2VucmVfdHlwZT0xJnBhZ2U9e3twYWdlfX0nLAogICAgc2VhcmNoVXJsOiAnL2FwaS9zZWFyY2g/a2V5PSoqJnRhYl90eXBlPTImb2Zmc2V0PSgoZnlwYWdlLTEpKjEwKScsCiAgICBkZXRhaWxVcmw6ICcvYXBpL2RldGFpbD9ib29rX2lkPWZ5aWQnLAogICAgaGVhZGVyczogeydVc2VyLUFnZW50JzogJ1VDX1VBJ30sCiAgICBzZWFyY2hhYmxlOiAxLAogICAgcXVpY2tTZWFyY2g6IDAsCiAgICBmaWx0ZXJhYmxlOiAxLAogICAgZG91YmxlOiB0cnVlLAogICAgcGxheV9wYXJzZTogdHJ1ZSwKICAgIGxpbWl0OiAxMiwKICAgIGNsYXNzX25hbWU6ICfnsr7lk4HlsI/or7Qm55u45aOw6K+E5LmmJuS4lueVjOWOhuWPsiblkI3okZfop6Por7sm5a2m5Lmg5oiQ6ZW/JuaIj+absuiJuuacrybnlJ/mtLvnmb7np5Em5a625bqt5pWZ6IKyJuS6uuaWh+enkeWtpiblhbbku5YnLAogICAgY2xhc3NfdXJsOiAnODk5JjQ0NSYxMiYxMzImNDQ5JjExMyY5NjAmNDUwJjQ0NyYzOScsCiAgICBmaWx0ZXI6IHsKICAgICAgICAiODk5IjogW3trZXk6ICJ0eXBlIiwgbmFtZTogIuexu+WeiyIsIHZhbHVlOiBbeyJuIjoi5YWo6YOoIiwidiI6Ijg5OSJ9LHsibiI6IumDveW4giIsInYiOiIxIn0seyJuIjoi56m/6LaKIiwidiI6IjM3In0seyJuIjoi546w5Luj6KiA5oOFIiwidiI6IjMifSx7Im4iOiLlj6Tku6PoqIDmg4UiLCJ2IjoiNSJ9LHsibiI6IuaAu+ijgSIsInYiOiIyOSJ9LHsibiI6IueOhOW5uyIsInYiOiI3In0seyJuIjoi6YeN55SfIiwidiI6IjM2In0seyJuIjoi5oKs55aRIiwidiI6IjEwIn0seyJuIjoi54G15byCIiwidiI6IjEwMCJ9LHsibiI6Iuezu+e7nyIsInYiOiIxOSJ9LHsibiI6IuenjeeUsCIsInYiOiIyMyJ9LHsibiI6IueUnOWuoCIsInYiOiI5NiJ9LHsibiI6IuWuoOWmuyIsInYiOiIzMCJ9LHsibiI6IumDveW4gueUn+a0uyIsInYiOiIyIn0seyJuIjoi6LWY5am/IiwidiI6IjI1In0seyJuIjoi5YWI5ama5ZCO54ixIiwidiI6IjI2NSJ9LHsibiI6IuWuq+aWl+WuheaWlyIsInYiOiIyNDYifSx7Im4iOiLpg73luILml6XluLgiLCJ2IjoiMjYxIn0seyJuIjoi5oiY56We6LWY5am/IiwidiI6IjI3In0seyJuIjoi56We5Yy7IiwidiI6IjI2In0seyJuIjoi5b2x6KeG5bCP6K+0IiwidiI6IjQ1In0seyJuIjoi5Zu95YaF5b2x6KeGIiwidiI6Ijk5MSJ9LHsibiI6IuWbveWkluW9seinhiIsInYiOiI5OTIifSx7Im4iOiLnsr7lk4HlsI/or7QiLCJ2IjoiOTcwIn0seyJuIjoi5oKs55aR5o6o55CGIiwidiI6IjE2NSJ9LHsibiI6IuenkeW5u+Wwj+ivtCIsInYiOiIxNjYifSx7Im4iOiLmrabkvqDlsI/or7QiLCJ2IjoiOTkzIn0seyJuIjoi546E5bm75bCP6K+0IiwidiI6Ijk3MSJ9LHsibiI6IueOsOWunuWwj+ivtCIsInYiOiI0MDAifSx7Im4iOiLmg4XmhJ/lsI/or7QiLCJ2IjoiOTcyIn0seyJuIjoi5Lyg57uf546E5bm7IiwidiI6IjI1OCJ9LHsibiI6IueOi+WmgyIsInYiOiI4NSJ9LHsibiI6IuWlh+W5u+S7meS+oCIsInYiOiIyNTkifSx7Im4iOiLokIzlrp0iLCJ2IjoiMjgifSx7Im4iOiLpg73luILohJHmtJ4iLCJ2IjoiMjYyIn0seyJuIjoi6IGM5Zy6IiwidiI6IjEyNyJ9LHsibiI6IuWroeWlsyIsInYiOiI4OCJ9LHsibiI6IumDveW4guS/ruecnyIsInYiOiIxMjQifSx7Im4iOiLlubvmg7PoqIDmg4UiLCJ2IjoiMzIifSx7Im4iOiLnpZ7osaoiLCJ2IjoiMjAifSx7Im4iOiLnqbrpl7QiLCJ2IjoiNDQifSx7Im4iOiLlhbbku5YiLCJ2IjoiMzEifSx7Im4iOiLnjoTlubvoqIDmg4UiLCJ2IjoiMjQ4In0seyJuIjoi546E5bm76ISR5rSeIiwidiI6IjI1NyJ9LHsibiI6IuWOhuWPsuWPpOS7oyIsInYiOiIyNzMifSx7Im4iOiLnp5HlubvmnKvkuJYiLCJ2IjoiOCJ9LHsibiI6IuW5tOS7oyIsInYiOiI3OSJ9LHsibiI6IuWkqeaJjSIsInYiOiI5MCJ9LHsibiI6IuWls+W8uiIsInYiOiI4NiJ9LHsibiI6IuaOqOeQhiIsInYiOiI2MSJ9LHsibiI6IuiFuem7kSIsInYiOiI5MiJ9LHsibiI6IuivuOWkqeS4h+eVjCIsInYiOiI3MSJ9LHsibiI6IuWMu+acryIsInYiOiIyNDcifSx7Im4iOiLmmJ/pmYUiLCJ2IjoiNzcifSx7Im4iOiLpibTlrp0iLCJ2IjoiMTcifSx7Im4iOiLlm6LlrqAiLCJ2IjoiOTQifSx7Im4iOiLmia7njKrlkIPomY4iLCJ2IjoiOTMifSx7Im4iOiLmrabkvqAiLCJ2IjoiMTYifSx7Im4iOiLnjrDoqIDohJHmtJ4iLCJ2IjoiMjY3In0seyJuIjoi6YO95biC56eN55SwIiwidiI6IjI2MyJ9LHsibiI6IuaXoOaVjCIsInYiOiIzODQifSx7Im4iOiLnm5flopMiLCJ2IjoiODEifSx7Im4iOiLpqaznlLIiLCJ2IjoiMjY2In0seyJuIjoi55qH5ZCOIiwidiI6Ijg0In0seyJuIjoi54m556eN5YW1IiwidiI6IjM3NSJ9LHsibiI6IuWkp+WUkCIsInYiOiI3MyJ9LHsibiI6IuWFrOS4uyIsInYiOiI4MyJ9LHsibiI6IuWoseS5kOWciCIsInYiOiI0MyJ9LHsibiI6IumdkuaiheeruemprCIsInYiOiIzODcifSx7Im4iOiLlj6ToqIDohJHmtJ4iLCJ2IjoiMjUzIn0seyJuIjoi5Y6G5Y+y6ISR5rSeIiwidiI6IjI3MiJ9LHsibiI6Iuacq+S4liIsInYiOiI2OCJ9LHsibiI6IuWJkemBkyIsInYiOiI4MCJ9LHsibiI6IueOsOiogOeUnOWuoCIsInYiOiIzOTUifSx7Im4iOiLmuLjmiI/liqjmvKsiLCJ2IjoiNTcifSx7Im4iOiLmtKrojZIiLCJ2IjoiNjYifSx7Im4iOiLlv6vnqb8iLCJ2IjoiMjQifSx7Im4iOiLmmI7mnJ0iLCJ2IjoiMTI2In0seyJuIjoi5aSW5Y2WIiwidiI6Ijc1In0seyJuIjoi5qCh6IqxIiwidiI6IjM4NSJ9LHsibiI6IuWltueIuCIsInYiOiI0MiJ9LHsibiI6IuagoeWbrSIsInYiOiI0In0seyJuIjoi5LiJ5Zu9IiwidiI6IjY3In0seyJuIjoi55u05pKtIiwidiI6IjY5In0seyJuIjoi56m/5LmmIiwidiI6IjM4MiJ9LHsibiI6Iua1t+WymyIsInYiOiI0MCJ9LHsibiI6Iue+jumjnyIsInYiOiI3OCJ9LHsibiI6IuWPjea0viIsInYiOiIzNjkifSx7Im4iOiLnjrDoqIDlpI3ku4ciLCJ2IjoiMjY4In0seyJuIjoi6KW/5ri46KGN55SfIiwidiI6IjM3MyJ9LHsibiI6IuaxgueUnyIsInYiOiIzNzkifSx7Im4iOiLmsJHlm70iLCJ2IjoiMzkwIn0seyJuIjoi5a625bqtIiwidiI6IjEyNSJ9LHsibiI6IuWtpumcuCIsInYiOiI4MiJ9LHsibiI6Iueah+WPlCIsInYiOiI4NyJ9LHsibiI6IuWuoOeJqSIsInYiOiI3NCJ9LHsibiI6IuaXoENQIiwidiI6IjM5MiJ9LHsibiI6IuWls+aJrueUt+ijhSIsInYiOiIzODgifSx7Im4iOiLnvZHmuLgiLCJ2IjoiMzcyIn0seyJuIjoi55eF5aiHIiwidiI6IjM4MCJ9LHsibiI6IueyvueBtSIsInYiOiI4OSJ9LHsibiI6IuiZkOaWhyIsInYiOiI5NSJ9LHsibiI6IumDveW4gumdkuaYpSIsInYiOiIzOTYifSx7Im4iOiLmuIXnqb8iLCJ2IjoiNzYifV19XSwKICAgICAgICAiNDQ1IjogW3trZXk6ICJ0eXBlIiwgbmFtZTogIuexu+WeiyIsIHZhbHVlOiBbeyJuIjoi5YWo6YOoIiwidiI6IjQ0NSJ9LHsibiI6IumDreW+t+e6siIsInYiOiIxMjgifSx7Im4iOiLnlLDov57lhYMiLCJ2IjoiMjAzIn0seyJuIjoi5YiY5YWw6IqzIiwidiI6IjIwMiJ9LHsibiI6IuWwj+WTgSIsInYiOiIxMTQifSx7Im4iOiLoooHpmJTmiJAiLCJ2IjoiMjA0In0seyJuIjoi5Y2V55Sw6IqzIiwidiI6IjIwMSJ9LHsibiI6IuivhOS5piIsInYiOiIxMTAifSx7Im4iOiLnm7jlo7AiLCJ2IjoiMTExIn1dfV0sCiAgICAgICAgIjEyIjogW3trZXk6ICJ0eXBlIiwgbmFtZTogIuexu+WeiyIsIHZhbHVlOiBbeyJuIjoi5YWo6YOoIiwidiI6IjEyIn0seyJuIjoi6a2P5pmL5Y2X5YyX5pydIiwidiI6IjIwOSJ9LHsibiI6IuenpuaxiSIsInYiOiIyMDgifSx7Im4iOiLkuJbnlYzlj7IiLCJ2IjoiMjE0In0seyJuIjoi5ZSQ5a6LIiwidiI6IjIxMCJ9LHsibiI6IuaYjua4hSIsInYiOiIyMTEifSx7Im4iOiLov5HnjrDku6MiLCJ2IjoiMjEzIn0seyJuIjoi5Lit5Zu95Y+yIiwidiI6IjIxMiJ9LHsibiI6IuaImOS6ieWPsiIsInYiOiIyMDYifSx7Im4iOiLlkI3kurrkvKAiLCJ2IjoiMjA3In0seyJuIjoi5Lit5Zu95Y6G5Y+yIiwidiI6IjQwMiJ9LHsibiI6IuS4lueVjOWOhuWPsiIsInYiOiI0MDMifSx7Im4iOiLljoblj7LlsI/or7QiLCJ2IjoiOTg4In0seyJuIjoi5Y6G5Y+y5paH5YyWIiwidiI6IjI0MSJ9XX1dLAogICAgICAgICIxMzIiOiBbe2tleTogInR5cGUiLCBuYW1lOiAi57G75Z6LIiwgdmFsdWU6IFt7Im4iOiLlhajpg6giLCJ2IjoiMTMyIn0seyJuIjoi5oiY5LqJIiwidiI6Ijk3In0seyJuIjoi5Lit5Zu95ZCN6JGXIiwidiI6Ijk4In0seyJuIjoi5aSW5Zu95ZCN6JGXIiwidiI6Ijk5In0seyJuIjoi57uP566h5Yqx5b+XIiwidiI6IjI0MiJ9LHsibiI6IuS6uueJqeS8oOiusCIsInYiOiI0MDkifSx7Im4iOiLnu4/lhbjmloflraYiLCJ2IjoiMjQzIn0seyJuIjoi6Z2S5pil5paH5a2mIiwidiI6IjE2OCJ9LHsibiI6IuaImOS6ieWGm+aXhSIsInYiOiI5NzMifV19XSwKICAgICAgICAiNDQ5IjogW3trZXk6ICJ0eXBlIiwgbmFtZTogIuexu+WeiyIsIHZhbHVlOiBbeyJuIjoi5YWo6YOoIiwidiI6IjQ0OSJ9LHsibiI6IuazleW+iyIsInYiOiIxNDIifSx7Im4iOiLlv4PnkIYiLCJ2IjoiMTQ2In0seyJuIjoi5b+D55CG5a2mIiwidiI6IjQwNyJ9LHsibiI6IuWwkeWEv+W/g+eQhiIsInYiOiI5ODYifSx7Im4iOiLlv4PnkIbnlpfmhIgiLCJ2IjoiOTk0In0seyJuIjoi5oqV6LWE55CG6LSiIiwidiI6IjE0MyJ9LHsibiI6IuiBjOS4muiBjOWcuiIsInYiOiIxNDUifSx7Im4iOiLnu4/nrqHllYbkuJoiLCJ2IjoiMjM4In0seyJuIjoi5rKf6YCa6KGo6L6+IiwidiI6IjE0NCJ9LHsibiI6IuWKseW/l+aIkOWKnyIsInYiOiIyMzkifV19XSwKICAgICAgICAiMTEzIjogW3trZXk6ICJ0eXBlIiwgbmFtZTogIuexu+WeiyIsIHZhbHVlOiBbeyJuIjoi5YWo6YOoIiwidiI6IjExMyJ9LHsibiI6IuS6rOWJpyIsInYiOiIxNzEifSx7Im4iOiLotorliaciLCJ2IjoiMTcyIn0seyJuIjoi6buE5qKF5oiPIiwidiI6IjE3MyJ9LHsibiI6IuivhOWJpyIsInYiOiIxNzQifSx7Im4iOiLosavliaciLCJ2IjoiMTc1In0seyJuIjoi5piG5puyIiwidiI6IjE3NiJ9LHsibiI6IuiJuuacryIsInYiOiIxMzQifSx7Im4iOiLoibrmnK/mlofljJYiLCJ2IjoiOTQ1In0seyJuIjoi6Z+z5LmQ6Iie6LmIIiwidiI6Ijk0NiJ9LHsibiI6Iue7mOeUuyIsInYiOiI5NTkifSx7Im4iOiLmkYTlvbEiLCJ2IjoiOTYxIn0seyJuIjoi6Ym06LWP55CG6K66IiwidiI6Ijk2MiJ9XX1dLAogICAgICAgICI5NjAiOiBbe2tleTogInR5cGUiLCBuYW1lOiAi57G75Z6LIiwgdmFsdWU6IFt7Im4iOiLlhajpg6giLCJ2IjoiOTYwIn0seyJuIjoi576O6aOf54O56aWqIiwidiI6IjI0MCJ9LHsibiI6IuaXhea4uCIsInYiOiIxNTgifSx7Im4iOiLlgaXlurflhbvnlJ8iLCJ2IjoiMTU5In0seyJuIjoi6L+Q5Yqo5YGl6LqrIiwidiI6IjQxNyJ9LHsibiI6IuaXtuWwmue+juWmhiIsInYiOiI0MTgifSx7Im4iOiLnvo7po5/kvJHpl7IiLCJ2IjoiNDE5In0seyJuIjoi5a625bGF5peF5ri4IiwidiI6IjQyMCJ9LHsibiI6IumjjuawtOWNoOWNnCIsInYiOiI0MjEifSx7Im4iOiLnmb7np5HluLjor4YiLCJ2IjoiOTYzIn0seyJuIjoi55Sf5rS75oOF5oSfIiwidiI6Ijk2NCJ9XX1dLAogICAgICAgICI0NTAiOiBbe2tleTogInR5cGUiLCBuYW1lOiAi57G75Z6LIiwgdmFsdWU6IFt7Im4iOiLlhajpg6giLCJ2IjoiNDUwIn0seyJuIjoi5Lqy5a2QIiwidiI6IjQ0NyJ9LHsibiI6IuWpmuWnuyIsInYiOiIyMzQifSx7Im4iOiLnvo7mlociLCJ2IjoiMjMwIn0seyJuIjoi5aSc6K+dIiwidiI6IjIzMiJ9LHsibiI6IuWls+aApyIsInYiOiIyMzEifSx7Im4iOiLlv4PnkIblgaXlurciLCJ2IjoiMjMzIn0seyJuIjoi5oOF5oSf5pWF5LqLIiwidiI6IjIyOSJ9LHsibiI6IuS6p+WQjuaKpOeQhiIsInYiOiIyMjYifSx7Im4iOiLlhL/nq6Xoi7Hor60iLCJ2IjoiMjIyIn0seyJuIjoi5a2V5pyf5L+d5YGlIiwidiI6IjIyNyJ9LHsibiI6IuS6suWtkOWBpeW6tyIsInYiOiIyMjgifSx7Im4iOiLnp5Hmma4iLCJ2IjoiMjIzIn0seyJuIjoi5Zu95a2m5Y6G5Y+yIiwidiI6IjIyNCJ9LHsibiI6IuiDjuaVmeaXqeaVmSIsInYiOiIyMjUifSx7Im4iOiLlhL/nq6XmloflraYiLCJ2IjoiMjIwIn1dfV0sCiAgICAgICAgIjQ0NyI6IFt7a2V5OiAidHlwZSIsIG5hbWU6ICLnsbvlnosiLCB2YWx1ZTogW3sibiI6IuWFqOmDqCIsInYiOiI0NDcifSx7Im4iOiLnp5HlrabmioDmnK8iLCJ2IjoiMTAwMCJ9LHsibiI6IuiHqueEtuenkeWtpiIsInYiOiIxMzYifSx7Im4iOiLkvZvlraYiLCJ2IjoiMTE1In0seyJuIjoi56S+56eRIiwidiI6IjIzNyJ9LHsibiI6IuWbveWtpiIsInYiOiIxMTYifSx7Im4iOiLmlaPmlofmiI/liaciLCJ2IjoiMjM2In0seyJuIjoi5paH5YyWIiwidiI6IjExOCJ9LHsibiI6IuaKgOacryIsInYiOiI5NjkifSx7Im4iOiLnp5HlraYiLCJ2IjoiOTc5In0seyJuIjoi5bel5LiaIiwidiI6Ijk4MCJ9LHsibiI6IuWGnOael+eJp+a4lCIsInYiOiI5ODEifSx7Im4iOiLorqHnrpfmnLoiLCJ2IjoiOTgyIn0seyJuIjoi5bu6562R5Zut5p6XIiwidiI6Ijk4MyJ9XX1dLAogICAgICAgICIzOSI6IFt7a2V5OiAidHlwZSIsIG5hbWU6ICLnsbvlnosiLCB2YWx1ZTogW3sibiI6IuS6jOasoeWFgyIsInYiOiIzOSJ9LHsibiI6IuWoseS5kCIsInYiOiIxMjEifSx7Im4iOiLnuqrlvZUiLCJ2IjoiMTMzIn0seyJuIjoi5paw6Ze7IiwidiI6IjQ0NiJ9XX1dCiAgICB9LAoKICAgIOS4gOe6pzogYXN5bmMgZnVuY3Rpb24gKHRpZCwgcGcsIGZpbHRlciwgZXh0ZW5kKSB7CiAgICAgICAgbGV0IHBhZ2UgPSBwZyB8fCAxOwogICAgICAgIGxldCB0eXBlSWQgPSAoZXh0ZW5kICYmIGV4dGVuZC50eXBlKSA/IGV4dGVuZC50eXBlIDogdGlkOwogICAgICAgIGxldCBmaW5hbFVybCA9IGAke3RoaXMuaG9zdH0vYXBpL2Rpc2NvdmVyP3RhYj3lkKzkuaYmdHlwZT0ke3R5cGVJZH0mZ2VuZGVyPTImZ2VucmVfdHlwZT0xJnBhZ2U9JHtwYWdlfWA7CiAgICAgICAgbGV0IGpzb24gPSBhd2FpdCByZXF1ZXN0KGZpbmFsVXJsKTsKICAgICAgICBsZXQgZGF0YSA9IEpTT04ucGFyc2UoanNvbik7CiAgICAgICAgbGV0IGJvb2tfbGlzdCA9IGRhdGEuY29kZSA9PT0gMjAwID8gZGF0YS5kYXRhIDogW107CiAgICAgICAgbGV0IGQgPSBbXTsKICAgICAgICBib29rX2xpc3QuZm9yRWFjaCgoaXQpID0+IHsKICAgICAgICAgICAgZC5wdXNoKHsKICAgICAgICAgICAgICAgIHRpdGxlOiBpdC5ib29rX25hbWUgfHwgaXQuQm9va05hbWUsCiAgICAgICAgICAgICAgICB1cmw6IGl0LmJvb2tfaWQgfHwgaXQuQm9va0lkLAogICAgICAgICAgICAgICAgZGVzYzogaXQuYXV0aG9yIHx8IGl0LkF1dGhvciwKICAgICAgICAgICAgICAgIGNvbnRlbnQ6IGl0LmFic3RyYWN0IHx8IGl0LkFic3RyYWN0IHx8IGl0LmJvb2tfYWJzdHJhY3RfdjIsCiAgICAgICAgICAgICAgICBwaWNfdXJsOiBpdC50aHVtYl91cmwgfHwgaXQuVGh1bWJVUkwgfHwgaXQuYXVkaW9fdGh1bWJfdXJpCiAgICAgICAgICAgIH0pOwogICAgICAgIH0pOwoKICAgICAgICByZXR1cm4gc2V0UmVzdWx0KGQpOwogICAgfSwKCiAgICDkuoznuqc6IGFzeW5jIGZ1bmN0aW9uICgpIHsKICAgICAgICBsZXQgZGV0YWlsQXBpID0gYCR7dGhpcy5ob3N0fS9hcGkvZGV0YWlsP2Jvb2tfaWQ9JHt0aGlzLm9ySWR9YDsKICAgICAgICBsZXQgZGV0YWlsSnNvbiA9IGF3YWl0IHJlcXVlc3QoZGV0YWlsQXBpKTsKICAgICAgICBsZXQgZGV0YWlsRGF0YSA9IEpTT04ucGFyc2UoZGV0YWlsSnNvbik7CiAgICAgICAgbGV0IGRhdGEgPSBkZXRhaWxEYXRhLmRhdGEuZGF0YTsKICAgICAgICBsZXQgY2hhcHRlcnNBcGkgPSBgJHt0aGlzLmhvc3R9L2FwaS9ib29rP2Jvb2tfaWQ9JHt0aGlzLm9ySWR9YDsKICAgICAgICBsZXQgY2hhcHRlcnNKc29uID0gYXdhaXQgcmVxdWVzdChjaGFwdGVyc0FwaSk7CiAgICAgICAgbGV0IGNoYXB0ZXJzRGF0YSA9IEpTT04ucGFyc2UoY2hhcHRlcnNKc29uKTsKICAgICAgICBsZXQgYm9va0RhdGEgPSBjaGFwdGVyc0RhdGEuZGF0YS5kYXRhOwogICAgICAgIGxldCBsaXN0ID0gYm9va0RhdGEuY2hhcHRlckxpc3RXaXRoVm9sdW1lPy5mbGF0KCkgfHwgYm9va0RhdGEuY2hhcHRlckxpc3QgfHwgW107CiAgICAgICAgbGV0IHVybHMgPSBsaXN0Lm1hcChpdCA9PiBpdC50aXRsZSArICckJyArIGl0Lml0ZW1JZCArICdAJyArIGl0LnRpdGxlKS5qb2luKCcjJyk7CiAgICAgICAgcmV0dXJuIHsKICAgICAgICAgICAgdm9kX2lkOiB0aGlzLm9ySWQsCiAgICAgICAgICAgIHZvZF9uYW1lOiBkYXRhLmJvb2tfbmFtZSwKICAgICAgICAgICAgdHlwZV9uYW1lOiBkYXRhLmNhdGVnb3J5LAogICAgICAgICAgICB2b2RfcGljOiBkYXRhLnRodW1iX3VybCB8fCBkYXRhLmV4cGFuZF90aHVtYl91cmwsCiAgICAgICAgICAgIHZvZF9jb250ZW50OiBkYXRhLmFic3RyYWN0IHx8IGRhdGEuYm9va19hYnN0cmFjdF92MiwKICAgICAgICAgICAgdm9kX3JlbWFya3M6IGRhdGEuc3ViX2luZm8sCiAgICAgICAgICAgIHZvZF9kaXJlY3RvcjogZGF0YS5hdXRob3IsCiAgICAgICAgICAgIHZvZF9wbGF5X2Zyb206ICfnlarojITnlYXlkKwnLAogICAgICAgICAgICB2b2RfcGxheV91cmw6IHVybHMKICAgICAgICB9OwogICAgfSwKCiAgICDmkJzntKI6IGFzeW5jIGZ1bmN0aW9uICgpIHsKICAgICAgICBsZXQge2lucHV0fSA9IHRoaXM7CiAgICAgICAgbGV0IGh0bWwgPSBhd2FpdCByZXF1ZXN0KGlucHV0KTsKICAgICAgICBsZXQganNvbiA9IEpTT04ucGFyc2UoaHRtbCk7CiAgICAgICAgbGV0IGRhdGEgPSBqc29uLmRhdGEuc2VhcmNoX3RhYnNbNF0uZGF0YTsKICAgICAgICBsZXQgZCA9IFtdOwogICAgICAgIGZvciAobGV0IGl0IG9mIGRhdGEuZmlsdGVyKGkgPT4gaS5ib29rX2RhdGEpKSB7CiAgICAgICAgICAgIGxldCBib29rID0gaXQuYm9va19kYXRhWzBdOwogICAgICAgICAgICBkLnB1c2goewogICAgICAgICAgICAgICAgdGl0bGU6IGJvb2suYm9va19uYW1lLAogICAgICAgICAgICAgICAgdXJsOiBib29rLmJvb2tfaWQsCiAgICAgICAgICAgICAgICBkZXNjOiBib29rLmF1dGhvciwKICAgICAgICAgICAgICAgIGNvbnRlbnQ6IGJvb2suYm9va19hYnN0cmFjdCB8fCBib29rLmFic3RyYWN0LAogICAgICAgICAgICAgICAgcGljX3VybDogYm9vay50aHVtYl91cmwKICAgICAgICAgICAgfSk7CiAgICAgICAgfQogICAgICAgIHJldHVybiBzZXRSZXN1bHQoZCk7CiAgICB9LAoKICAgIGxhenk6IGFzeW5jIGZ1bmN0aW9uICgpIHsKICAgICAgICBsZXQge2lucHV0fSA9IHRoaXM7CiAgICAgICAgbGV0IHBhcnRzID0gaW5wdXQuc3BsaXQoJ0AnKTsKICAgICAgICBsZXQgaXRlbUlkID0gcGFydHNbMF07CiAgICAgICAgbGV0IHRvbmVJZCA9ICcxJzsKICAgICAgICBsZXQgY29udGVudF91cmwgPSBgJHt0aGlzLmhvc3R9L2FwaS9jb250ZW50P2l0ZW1faWQ9JHtpdGVtSWR9JnRhYj3lkKzkuaYmdG9uZV9pZD0ke3RvbmVJZH1gOwogICAgICAgIGxldCBqc29uU3RyID0gYXdhaXQgcmVxdWVzdChjb250ZW50X3VybCk7CiAgICAgICAgbGV0IGRhdGEgPSBKU09OLnBhcnNlKGpzb25TdHIpOwogICAgICAgIHJldHVybiB7cGFyc2U6IDAsIHVybDogZGF0YS5kYXRhLmNvbnRlbnR9OwogICAgfQp9Ow== \ No newline at end of file +dmFyIHJ1bGUgPSB7CiAgICDnsbvlnos6ICflkKzkuaYnLAogICAgYXV0aG9yOiAnRXlsaW5TaXInLAogICAgdGl0bGU6ICfnlarojITnlYXlkKwnLAogICAgaG9zdDogJ2h0dHBzOi8vcWtmcWFwaS52djl2LmNuJywKICAgIHVybDogJy9hcGkvZGlzY292ZXI/dGFiPeWQrOS5piZ0eXBlPWZ5Y2xhc3MmZ2VuZGVyPTImZ2VucmVfdHlwZT0xJnBhZ2U9e3twYWdlfX0nLAogICAgc2VhcmNoVXJsOiAnL2FwaS9zZWFyY2g/a2V5PSoqJnRhYl90eXBlPTImb2Zmc2V0PSgoZnlwYWdlLTEpKjEwKScsCiAgICBkZXRhaWxVcmw6ICcvYXBpL2RldGFpbD9ib29rX2lkPWZ5aWQnLAogICAgaGVhZGVyczogeydVc2VyLUFnZW50JzogJ1VDX1VBJ30sCiAgICBzZWFyY2hhYmxlOiAxLAogICAgcXVpY2tTZWFyY2g6IDAsCiAgICBmaWx0ZXJhYmxlOiAxLAogICAgZG91YmxlOiB0cnVlLAogICAgcGxheV9wYXJzZTogdHJ1ZSwKICAgIGxpbWl0OiAxMiwKICAgIGNsYXNzX25hbWU6ICfnsr7lk4HlsI/or7Qm55u45aOw6K+E5LmmJuS4lueVjOWOhuWPsiblkI3okZfop6Por7sm5a2m5Lmg5oiQ6ZW/JuaIj+absuiJuuacrybnlJ/mtLvnmb7np5Em5a625bqt5pWZ6IKyJuS6uuaWh+enkeWtpiblhbbku5YnLAogICAgY2xhc3NfdXJsOiAnODk5JjQ0NSYxMiYxMzImNDQ5JjExMyY5NjAmNDUwJjQ0NyYzOScsCiAgICBmaWx0ZXI6IHsKICAgICAgICAiODk5IjogW3trZXk6ICJ0eXBlIiwgbmFtZTogIuexu+WeiyIsIHZhbHVlOiBbeyJuIjoi5YWo6YOoIiwidiI6Ijg5OSJ9LHsibiI6IumDveW4giIsInYiOiIxIn0seyJuIjoi56m/6LaKIiwidiI6IjM3In0seyJuIjoi546w5Luj6KiA5oOFIiwidiI6IjMifSx7Im4iOiLlj6Tku6PoqIDmg4UiLCJ2IjoiNSJ9LHsibiI6IuaAu+ijgSIsInYiOiIyOSJ9LHsibiI6IueOhOW5uyIsInYiOiI3In0seyJuIjoi6YeN55SfIiwidiI6IjM2In0seyJuIjoi5oKs55aRIiwidiI6IjEwIn0seyJuIjoi54G15byCIiwidiI6IjEwMCJ9LHsibiI6Iuezu+e7nyIsInYiOiIxOSJ9LHsibiI6IuenjeeUsCIsInYiOiIyMyJ9LHsibiI6IueUnOWuoCIsInYiOiI5NiJ9LHsibiI6IuWuoOWmuyIsInYiOiIzMCJ9LHsibiI6IumDveW4gueUn+a0uyIsInYiOiIyIn0seyJuIjoi6LWY5am/IiwidiI6IjI1In0seyJuIjoi5YWI5ama5ZCO54ixIiwidiI6IjI2NSJ9LHsibiI6IuWuq+aWl+WuheaWlyIsInYiOiIyNDYifSx7Im4iOiLpg73luILml6XluLgiLCJ2IjoiMjYxIn0seyJuIjoi5oiY56We6LWY5am/IiwidiI6IjI3In0seyJuIjoi56We5Yy7IiwidiI6IjI2In0seyJuIjoi5b2x6KeG5bCP6K+0IiwidiI6IjQ1In0seyJuIjoi5Zu95YaF5b2x6KeGIiwidiI6Ijk5MSJ9LHsibiI6IuWbveWkluW9seinhiIsInYiOiI5OTIifSx7Im4iOiLnsr7lk4HlsI/or7QiLCJ2IjoiOTcwIn0seyJuIjoi5oKs55aR5o6o55CGIiwidiI6IjE2NSJ9LHsibiI6IuenkeW5u+Wwj+ivtCIsInYiOiIxNjYifSx7Im4iOiLmrabkvqDlsI/or7QiLCJ2IjoiOTkzIn0seyJuIjoi546E5bm75bCP6K+0IiwidiI6Ijk3MSJ9LHsibiI6IueOsOWunuWwj+ivtCIsInYiOiI0MDAifSx7Im4iOiLmg4XmhJ/lsI/or7QiLCJ2IjoiOTcyIn0seyJuIjoi5Lyg57uf546E5bm7IiwidiI6IjI1OCJ9LHsibiI6IueOi+WmgyIsInYiOiI4NSJ9LHsibiI6IuWlh+W5u+S7meS+oCIsInYiOiIyNTkifSx7Im4iOiLokIzlrp0iLCJ2IjoiMjgifSx7Im4iOiLpg73luILohJHmtJ4iLCJ2IjoiMjYyIn0seyJuIjoi6IGM5Zy6IiwidiI6IjEyNyJ9LHsibiI6IuWroeWlsyIsInYiOiI4OCJ9LHsibiI6IumDveW4guS/ruecnyIsInYiOiIxMjQifSx7Im4iOiLlubvmg7PoqIDmg4UiLCJ2IjoiMzIifSx7Im4iOiLnpZ7osaoiLCJ2IjoiMjAifSx7Im4iOiLnqbrpl7QiLCJ2IjoiNDQifSx7Im4iOiLlhbbku5YiLCJ2IjoiMzEifSx7Im4iOiLnjoTlubvoqIDmg4UiLCJ2IjoiMjQ4In0seyJuIjoi546E5bm76ISR5rSeIiwidiI6IjI1NyJ9LHsibiI6IuWOhuWPsuWPpOS7oyIsInYiOiIyNzMifSx7Im4iOiLnp5HlubvmnKvkuJYiLCJ2IjoiOCJ9LHsibiI6IuW5tOS7oyIsInYiOiI3OSJ9LHsibiI6IuWkqeaJjSIsInYiOiI5MCJ9LHsibiI6IuWls+W8uiIsInYiOiI4NiJ9LHsibiI6IuaOqOeQhiIsInYiOiI2MSJ9LHsibiI6IuiFuem7kSIsInYiOiI5MiJ9LHsibiI6IuivuOWkqeS4h+eVjCIsInYiOiI3MSJ9LHsibiI6IuWMu+acryIsInYiOiIyNDcifSx7Im4iOiLmmJ/pmYUiLCJ2IjoiNzcifSx7Im4iOiLpibTlrp0iLCJ2IjoiMTcifSx7Im4iOiLlm6LlrqAiLCJ2IjoiOTQifSx7Im4iOiLmia7njKrlkIPomY4iLCJ2IjoiOTMifSx7Im4iOiLmrabkvqAiLCJ2IjoiMTYifSx7Im4iOiLnjrDoqIDohJHmtJ4iLCJ2IjoiMjY3In0seyJuIjoi6YO95biC56eN55SwIiwidiI6IjI2MyJ9LHsibiI6IuaXoOaVjCIsInYiOiIzODQifSx7Im4iOiLnm5flopMiLCJ2IjoiODEifSx7Im4iOiLpqaznlLIiLCJ2IjoiMjY2In0seyJuIjoi55qH5ZCOIiwidiI6Ijg0In0seyJuIjoi54m556eN5YW1IiwidiI6IjM3NSJ9LHsibiI6IuWkp+WUkCIsInYiOiI3MyJ9LHsibiI6IuWFrOS4uyIsInYiOiI4MyJ9LHsibiI6IuWoseS5kOWciCIsInYiOiI0MyJ9LHsibiI6IumdkuaiheeruemprCIsInYiOiIzODcifSx7Im4iOiLlj6ToqIDohJHmtJ4iLCJ2IjoiMjUzIn0seyJuIjoi5Y6G5Y+y6ISR5rSeIiwidiI6IjI3MiJ9LHsibiI6Iuacq+S4liIsInYiOiI2OCJ9LHsibiI6IuWJkemBkyIsInYiOiI4MCJ9LHsibiI6IueOsOiogOeUnOWuoCIsInYiOiIzOTUifSx7Im4iOiLmuLjmiI/liqjmvKsiLCJ2IjoiNTcifSx7Im4iOiLmtKrojZIiLCJ2IjoiNjYifSx7Im4iOiLlv6vnqb8iLCJ2IjoiMjQifSx7Im4iOiLmmI7mnJ0iLCJ2IjoiMTI2In0seyJuIjoi5aSW5Y2WIiwidiI6Ijc1In0seyJuIjoi5qCh6IqxIiwidiI6IjM4NSJ9LHsibiI6IuWltueIuCIsInYiOiI0MiJ9LHsibiI6IuagoeWbrSIsInYiOiI0In0seyJuIjoi5LiJ5Zu9IiwidiI6IjY3In0seyJuIjoi55u05pKtIiwidiI6IjY5In0seyJuIjoi56m/5LmmIiwidiI6IjM4MiJ9LHsibiI6Iua1t+WymyIsInYiOiI0MCJ9LHsibiI6Iue+jumjnyIsInYiOiI3OCJ9LHsibiI6IuWPjea0viIsInYiOiIzNjkifSx7Im4iOiLnjrDoqIDlpI3ku4ciLCJ2IjoiMjY4In0seyJuIjoi6KW/5ri46KGN55SfIiwidiI6IjM3MyJ9LHsibiI6IuaxgueUnyIsInYiOiIzNzkifSx7Im4iOiLmsJHlm70iLCJ2IjoiMzkwIn0seyJuIjoi5a625bqtIiwidiI6IjEyNSJ9LHsibiI6IuWtpumcuCIsInYiOiI4MiJ9LHsibiI6Iueah+WPlCIsInYiOiI4NyJ9LHsibiI6IuWuoOeJqSIsInYiOiI3NCJ9LHsibiI6IuaXoENQIiwidiI6IjM5MiJ9LHsibiI6IuWls+aJrueUt+ijhSIsInYiOiIzODgifSx7Im4iOiLnvZHmuLgiLCJ2IjoiMzcyIn0seyJuIjoi55eF5aiHIiwidiI6IjM4MCJ9LHsibiI6IueyvueBtSIsInYiOiI4OSJ9LHsibiI6IuiZkOaWhyIsInYiOiI5NSJ9LHsibiI6IumDveW4gumdkuaYpSIsInYiOiIzOTYifSx7Im4iOiLmuIXnqb8iLCJ2IjoiNzYifV19XSwKICAgICAgICAiNDQ1IjogW3trZXk6ICJ0eXBlIiwgbmFtZTogIuexu+WeiyIsIHZhbHVlOiBbeyJuIjoi5YWo6YOoIiwidiI6IjQ0NSJ9LHsibiI6IumDreW+t+e6siIsInYiOiIxMjgifSx7Im4iOiLnlLDov57lhYMiLCJ2IjoiMjAzIn0seyJuIjoi5YiY5YWw6IqzIiwidiI6IjIwMiJ9LHsibiI6IuWwj+WTgSIsInYiOiIxMTQifSx7Im4iOiLoooHpmJTmiJAiLCJ2IjoiMjA0In0seyJuIjoi5Y2V55Sw6IqzIiwidiI6IjIwMSJ9LHsibiI6IuivhOS5piIsInYiOiIxMTAifSx7Im4iOiLnm7jlo7AiLCJ2IjoiMTExIn1dfV0sCiAgICAgICAgIjEyIjogW3trZXk6ICJ0eXBlIiwgbmFtZTogIuexu+WeiyIsIHZhbHVlOiBbeyJuIjoi5YWo6YOoIiwidiI6IjEyIn0seyJuIjoi6a2P5pmL5Y2X5YyX5pydIiwidiI6IjIwOSJ9LHsibiI6IuenpuaxiSIsInYiOiIyMDgifSx7Im4iOiLkuJbnlYzlj7IiLCJ2IjoiMjE0In0seyJuIjoi5ZSQ5a6LIiwidiI6IjIxMCJ9LHsibiI6IuaYjua4hSIsInYiOiIyMTEifSx7Im4iOiLov5HnjrDku6MiLCJ2IjoiMjEzIn0seyJuIjoi5Lit5Zu95Y+yIiwidiI6IjIxMiJ9LHsibiI6IuaImOS6ieWPsiIsInYiOiIyMDYifSx7Im4iOiLlkI3kurrkvKAiLCJ2IjoiMjA3In0seyJuIjoi5Lit5Zu95Y6G5Y+yIiwidiI6IjQwMiJ9LHsibiI6IuS4lueVjOWOhuWPsiIsInYiOiI0MDMifSx7Im4iOiLljoblj7LlsI/or7QiLCJ2IjoiOTg4In0seyJuIjoi5Y6G5Y+y5paH5YyWIiwidiI6IjI0MSJ9XX1dLAogICAgICAgICIxMzIiOiBbe2tleTogInR5cGUiLCBuYW1lOiAi57G75Z6LIiwgdmFsdWU6IFt7Im4iOiLlhajpg6giLCJ2IjoiMTMyIn0seyJuIjoi5oiY5LqJIiwidiI6Ijk3In0seyJuIjoi5Lit5Zu95ZCN6JGXIiwidiI6Ijk4In0seyJuIjoi5aSW5Zu95ZCN6JGXIiwidiI6Ijk5In0seyJuIjoi57uP566h5Yqx5b+XIiwidiI6IjI0MiJ9LHsibiI6IuS6uueJqeS8oOiusCIsInYiOiI0MDkifSx7Im4iOiLnu4/lhbjmloflraYiLCJ2IjoiMjQzIn0seyJuIjoi6Z2S5pil5paH5a2mIiwidiI6IjE2OCJ9LHsibiI6IuaImOS6ieWGm+aXhSIsInYiOiI5NzMifV19XSwKICAgICAgICAiNDQ5IjogW3trZXk6ICJ0eXBlIiwgbmFtZTogIuexu+WeiyIsIHZhbHVlOiBbeyJuIjoi5YWo6YOoIiwidiI6IjQ0OSJ9LHsibiI6IuazleW+iyIsInYiOiIxNDIifSx7Im4iOiLlv4PnkIYiLCJ2IjoiMTQ2In0seyJuIjoi5b+D55CG5a2mIiwidiI6IjQwNyJ9LHsibiI6IuWwkeWEv+W/g+eQhiIsInYiOiI5ODYifSx7Im4iOiLlv4PnkIbnlpfmhIgiLCJ2IjoiOTk0In0seyJuIjoi5oqV6LWE55CG6LSiIiwidiI6IjE0MyJ9LHsibiI6IuiBjOS4muiBjOWcuiIsInYiOiIxNDUifSx7Im4iOiLnu4/nrqHllYbkuJoiLCJ2IjoiMjM4In0seyJuIjoi5rKf6YCa6KGo6L6+IiwidiI6IjE0NCJ9LHsibiI6IuWKseW/l+aIkOWKnyIsInYiOiIyMzkifV19XSwKICAgICAgICAiMTEzIjogW3trZXk6ICJ0eXBlIiwgbmFtZTogIuexu+WeiyIsIHZhbHVlOiBbeyJuIjoi5YWo6YOoIiwidiI6IjExMyJ9LHsibiI6IuS6rOWJpyIsInYiOiIxNzEifSx7Im4iOiLotorliaciLCJ2IjoiMTcyIn0seyJuIjoi6buE5qKF5oiPIiwidiI6IjE3MyJ9LHsibiI6IuivhOWJpyIsInYiOiIxNzQifSx7Im4iOiLosavliaciLCJ2IjoiMTc1In0seyJuIjoi5piG5puyIiwidiI6IjE3NiJ9LHsibiI6IuiJuuacryIsInYiOiIxMzQifSx7Im4iOiLoibrmnK/mlofljJYiLCJ2IjoiOTQ1In0seyJuIjoi6Z+z5LmQ6Iie6LmIIiwidiI6Ijk0NiJ9LHsibiI6Iue7mOeUuyIsInYiOiI5NTkifSx7Im4iOiLmkYTlvbEiLCJ2IjoiOTYxIn0seyJuIjoi6Ym06LWP55CG6K66IiwidiI6Ijk2MiJ9XX1dLAogICAgICAgICI5NjAiOiBbe2tleTogInR5cGUiLCBuYW1lOiAi57G75Z6LIiwgdmFsdWU6IFt7Im4iOiLlhajpg6giLCJ2IjoiOTYwIn0seyJuIjoi576O6aOf54O56aWqIiwidiI6IjI0MCJ9LHsibiI6IuaXhea4uCIsInYiOiIxNTgifSx7Im4iOiLlgaXlurflhbvnlJ8iLCJ2IjoiMTU5In0seyJuIjoi6L+Q5Yqo5YGl6LqrIiwidiI6IjQxNyJ9LHsibiI6IuaXtuWwmue+juWmhiIsInYiOiI0MTgifSx7Im4iOiLnvo7po5/kvJHpl7IiLCJ2IjoiNDE5In0seyJuIjoi5a625bGF5peF5ri4IiwidiI6IjQyMCJ9LHsibiI6IumjjuawtOWNoOWNnCIsInYiOiI0MjEifSx7Im4iOiLnmb7np5HluLjor4YiLCJ2IjoiOTYzIn0seyJuIjoi55Sf5rS75oOF5oSfIiwidiI6Ijk2NCJ9XX1dLAogICAgICAgICI0NTAiOiBbe2tleTogInR5cGUiLCBuYW1lOiAi57G75Z6LIiwgdmFsdWU6IFt7Im4iOiLlhajpg6giLCJ2IjoiNDUwIn0seyJuIjoi5Lqy5a2QIiwidiI6IjQ0NyJ9LHsibiI6IuWpmuWnuyIsInYiOiIyMzQifSx7Im4iOiLnvo7mlociLCJ2IjoiMjMwIn0seyJuIjoi5aSc6K+dIiwidiI6IjIzMiJ9LHsibiI6IuWls+aApyIsInYiOiIyMzEifSx7Im4iOiLlv4PnkIblgaXlurciLCJ2IjoiMjMzIn0seyJuIjoi5oOF5oSf5pWF5LqLIiwidiI6IjIyOSJ9LHsibiI6IuS6p+WQjuaKpOeQhiIsInYiOiIyMjYifSx7Im4iOiLlhL/nq6Xoi7Hor60iLCJ2IjoiMjIyIn0seyJuIjoi5a2V5pyf5L+d5YGlIiwidiI6IjIyNyJ9LHsibiI6IuS6suWtkOWBpeW6tyIsInYiOiIyMjgifSx7Im4iOiLnp5Hmma4iLCJ2IjoiMjIzIn0seyJuIjoi5Zu95a2m5Y6G5Y+yIiwidiI6IjIyNCJ9LHsibiI6IuiDjuaVmeaXqeaVmSIsInYiOiIyMjUifSx7Im4iOiLlhL/nq6XmloflraYiLCJ2IjoiMjIwIn1dfV0sCiAgICAgICAgIjQ0NyI6IFt7a2V5OiAidHlwZSIsIG5hbWU6ICLnsbvlnosiLCB2YWx1ZTogW3sibiI6IuWFqOmDqCIsInYiOiI0NDcifSx7Im4iOiLnp5HlrabmioDmnK8iLCJ2IjoiMTAwMCJ9LHsibiI6IuiHqueEtuenkeWtpiIsInYiOiIxMzYifSx7Im4iOiLkvZvlraYiLCJ2IjoiMTE1In0seyJuIjoi56S+56eRIiwidiI6IjIzNyJ9LHsibiI6IuWbveWtpiIsInYiOiIxMTYifSx7Im4iOiLmlaPmlofmiI/liaciLCJ2IjoiMjM2In0seyJuIjoi5paH5YyWIiwidiI6IjExOCJ9LHsibiI6IuaKgOacryIsInYiOiI5NjkifSx7Im4iOiLnp5HlraYiLCJ2IjoiOTc5In0seyJuIjoi5bel5LiaIiwidiI6Ijk4MCJ9LHsibiI6IuWGnOael+eJp+a4lCIsInYiOiI5ODEifSx7Im4iOiLorqHnrpfmnLoiLCJ2IjoiOTgyIn0seyJuIjoi5bu6562R5Zut5p6XIiwidiI6Ijk4MyJ9XX1dLAogICAgICAgICIzOSI6IFt7a2V5OiAidHlwZSIsIG5hbWU6ICLnsbvlnosiLCB2YWx1ZTogW3sibiI6IuS6jOasoeWFgyIsInYiOiIzOSJ9LHsibiI6IuWoseS5kCIsInYiOiIxMjEifSx7Im4iOiLnuqrlvZUiLCJ2IjoiMTMzIn0seyJuIjoi5paw6Ze7IiwidiI6IjQ0NiJ9XX1dCiAgICB9LAoKICAgIOS4gOe6pzogYXN5bmMgZnVuY3Rpb24gKHRpZCwgcGcsIGZpbHRlciwgZXh0ZW5kKSB7CiAgICAgICAgbGV0IHBhZ2UgPSBwZyB8fCAxOwogICAgICAgIGxldCB0eXBlSWQgPSAoZXh0ZW5kICYmIGV4dGVuZC50eXBlKSA/IGV4dGVuZC50eXBlIDogdGlkOwogICAgICAgIGxldCBmaW5hbFVybCA9IGAke3RoaXMuaG9zdH0vYXBpL2Rpc2NvdmVyP3RhYj3lkKzkuaYmdHlwZT0ke3R5cGVJZH0mZ2VuZGVyPTImZ2VucmVfdHlwZT0xJnBhZ2U9JHtwYWdlfWA7CiAgICAgICAgbGV0IGpzb24gPSBhd2FpdCByZXF1ZXN0KGZpbmFsVXJsKTsKICAgICAgICBsZXQgZGF0YSA9IEpTT04ucGFyc2UoanNvbik7CiAgICAgICAgbGV0IGJvb2tfbGlzdCA9IGRhdGEuY29kZSA9PT0gMjAwID8gZGF0YS5kYXRhIDogW107CiAgICAgICAgbGV0IGQgPSBbXTsKICAgICAgICBib29rX2xpc3QuZm9yRWFjaCgoaXQpID0+IHsKICAgICAgICAgICAgZC5wdXNoKHsKICAgICAgICAgICAgICAgIHRpdGxlOiBpdC5ib29rX25hbWUgfHwgaXQuQm9va05hbWUsCiAgICAgICAgICAgICAgICB1cmw6IGl0LmJvb2tfaWQgfHwgaXQuQm9va0lkLAogICAgICAgICAgICAgICAgZGVzYzogaXQuYXV0aG9yIHx8IGl0LkF1dGhvciwKICAgICAgICAgICAgICAgIGNvbnRlbnQ6IGl0LmFic3RyYWN0IHx8IGl0LkFic3RyYWN0IHx8IGl0LmJvb2tfYWJzdHJhY3RfdjIsCiAgICAgICAgICAgICAgICBwaWNfdXJsOiBpdC50aHVtYl91cmwgfHwgaXQuVGh1bWJVUkwgfHwgaXQuYXVkaW9fdGh1bWJfdXJpCiAgICAgICAgICAgIH0pOwogICAgICAgIH0pOwoKICAgICAgICByZXR1cm4gc2V0UmVzdWx0KGQpOwogICAgfSwKCiAgICDkuoznuqc6IGFzeW5jIGZ1bmN0aW9uICgpIHsKICAgICAgICBsZXQgZGV0YWlsQXBpID0gYCR7dGhpcy5ob3N0fS9hcGkvZGV0YWlsP2Jvb2tfaWQ9JHt0aGlzLm9ySWR9YDsKICAgICAgICBsZXQgZGV0YWlsSnNvbiA9IGF3YWl0IHJlcXVlc3QoZGV0YWlsQXBpKTsKICAgICAgICBsZXQgZGV0YWlsRGF0YSA9IEpTT04ucGFyc2UoZGV0YWlsSnNvbik7CiAgICAgICAgbGV0IGRhdGEgPSBkZXRhaWxEYXRhLmRhdGEuZGF0YTsKICAgICAgICBsZXQgY2hhcHRlcnNBcGkgPSBgJHt0aGlzLmhvc3R9L2FwaS9ib29rP2Jvb2tfaWQ9JHt0aGlzLm9ySWR9YDsKICAgICAgICBsZXQgY2hhcHRlcnNKc29uID0gYXdhaXQgcmVxdWVzdChjaGFwdGVyc0FwaSk7CiAgICAgICAgbGV0IGNoYXB0ZXJzRGF0YSA9IEpTT04ucGFyc2UoY2hhcHRlcnNKc29uKTsKICAgICAgICBsZXQgYm9va0RhdGEgPSBjaGFwdGVyc0RhdGEuZGF0YS5kYXRhOwogICAgICAgIGxldCBsaXN0ID0gYm9va0RhdGEuY2hhcHRlckxpc3RXaXRoVm9sdW1lPy5mbGF0KCkgfHwgYm9va0RhdGEuY2hhcHRlckxpc3QgfHwgW107CiAgICAgICAgbGV0IHVybHMgPSBsaXN0Lm1hcChpdCA9PiBpdC50aXRsZSArICckJyArIGl0Lml0ZW1JZCArICdAJyArIGl0LnRpdGxlKS5qb2luKCcjJyk7CiAgICAgICAgcmV0dXJuIHsKICAgICAgICAgICAgdm9kX2lkOiB0aGlzLm9ySWQsCiAgICAgICAgICAgIHZvZF9uYW1lOiBkYXRhLmJvb2tfbmFtZSwKICAgICAgICAgICAgdHlwZV9uYW1lOiBkYXRhLmNhdGVnb3J5LAogICAgICAgICAgICB2b2RfcGljOiBkYXRhLnRodW1iX3VybCB8fCBkYXRhLmV4cGFuZF90aHVtYl91cmwsCiAgICAgICAgICAgIHZvZF9jb250ZW50OiBkYXRhLmFic3RyYWN0IHx8IGRhdGEuYm9va19hYnN0cmFjdF92MiwKICAgICAgICAgICAgdm9kX3JlbWFya3M6IGRhdGEuc3ViX2luZm8sCiAgICAgICAgICAgIHZvZF9kaXJlY3RvcjogZGF0YS5hdXRob3IsCiAgICAgICAgICAgIHZvZF9wbGF5X2Zyb206ICfnlarojITnlYXlkKwnLAogICAgICAgICAgICB2b2RfcGxheV91cmw6IHVybHMKICAgICAgICB9OwogICAgfSwKCiAgICDmkJzntKI6IGFzeW5jIGZ1bmN0aW9uICgpIHsKICAgICAgICBsZXQge2lucHV0fSA9IHRoaXM7CiAgICAgICAgbGV0IGh0bWwgPSBhd2FpdCByZXF1ZXN0KGlucHV0KTsKICAgICAgICBsZXQganNvbiA9IEpTT04ucGFyc2UoaHRtbCk7CiAgICAgICAgbGV0IGRhdGEgPSBqc29uLmRhdGEuc2VhcmNoX3RhYnNbNF0uZGF0YTsKICAgICAgICBsZXQgZCA9IFtdOwogICAgICAgIGZvciAobGV0IGl0IG9mIGRhdGEuZmlsdGVyKGkgPT4gaS5ib29rX2RhdGEpKSB7CiAgICAgICAgICAgIGxldCBib29rID0gaXQuYm9va19kYXRhWzBdOwogICAgICAgICAgICBkLnB1c2goewogICAgICAgICAgICAgICAgdGl0bGU6IGJvb2suYm9va19uYW1lLAogICAgICAgICAgICAgICAgdXJsOiBib29rLmJvb2tfaWQsCiAgICAgICAgICAgICAgICBkZXNjOiBib29rLmF1dGhvciwKICAgICAgICAgICAgICAgIGNvbnRlbnQ6IGJvb2suYm9va19hYnN0cmFjdCB8fCBib29rLmFic3RyYWN0LAogICAgICAgICAgICAgICAgcGljX3VybDogYm9vay50aHVtYl91cmwKICAgICAgICAgICAgfSk7CiAgICAgICAgfQogICAgICAgIHJldHVybiBzZXRSZXN1bHQoZCk7CiAgICB9LAoKICAgIGxhenk6IGFzeW5jIGZ1bmN0aW9uICgpIHsKICAgICAgICBsZXQge2lucHV0fSA9IHRoaXM7CiAgICAgICAgbGV0IHBhcnRzID0gaW5wdXQuc3BsaXQoJ0AnKTsKICAgICAgICBsZXQgaXRlbUlkID0gcGFydHNbMF07CiAgICAgICAgbGV0IHRvbmVJZCA9ICcxJzsKICAgICAgICBsZXQgY29udGVudF91cmwgPSBgJHt0aGlzLmhvc3R9L2FwaS9jb250ZW50P2l0ZW1faWQ9JHtpdGVtSWR9JnRhYj3lkKzkuaYmdG9uZV9pZD0ke3RvbmVJZH1gOwogICAgICAgIGxldCBqc29uU3RyID0gYXdhaXQgcmVxdWVzdChjb250ZW50X3VybCk7CiAgICAgICAgbGV0IGRhdGEgPSBKU09OLnBhcnNlKGpzb25TdHIpOwogICAgICAgIHJldHVybiB7cGFyc2U6IDAsIHVybDogZGF0YS5kYXRhLmNvbnRlbnR9OwogICAgfQp9Ow== diff --git "a/spider/js/\347\225\252\350\214\204\347\237\255\345\211\247[\347\237\255].js" "b/spider/js/\347\225\252\350\214\204\347\237\255\345\211\247[\347\237\255].js" index ac10661c..001dd9a4 100644 --- "a/spider/js/\347\225\252\350\214\204\347\237\255\345\211\247[\347\237\255].js" +++ "b/spider/js/\347\225\252\350\214\204\347\237\255\345\211\247[\347\237\255].js" @@ -4,6 +4,7 @@ filterable: 0, quickSearch: 0, title: '番茄短剧', + logo: 'https://www.18zf.net/d/file/p/2023/1107/3ty5orktxrc.jpg', '类型': '影视', lang: 'ds' }) @@ -177,4 +178,4 @@ var rule = { }); return VODS }, -} \ No newline at end of file +} diff --git "a/spider/js/\347\237\255\345\211\247\347\275\221[\347\233\230].js" "b/spider/js/\347\237\255\345\211\247\347\275\221[\347\233\230].js" new file mode 100644 index 00000000..2ac60d23 --- /dev/null +++ "b/spider/js/\347\237\255\345\211\247\347\275\221[\347\233\230].js" @@ -0,0 +1,68 @@ +/* +@header({ + searchable: 1, + filterable: 0, + quickSearch: 0, + title: '短剧网', + author: 'EylinSir', + '类型': '影视', + lang: 'ds' +}) +*/ + +var rule = { + 类型: '影视', + author: 'EylinSir', + title: '短剧网', + host: 'https://sm3.cc', + url: '/?cate=fyclass&page=fypage', + searchUrl: '/search.php?q={wd}&page={pg}', + searchable: 1, + quickSearch: 0, + timeout: 5000, + play_parse: true, + class_name: '短剧大全&更新短剧', + class_url: '1&2', + headers: { 'User-Agent': 'MOBILE_UA' }, + + lazy: async function () { + return { url: this.input, parse: 0 }; + }, + + 推荐: async function() { + return await this.一级(); + }, + + 一级: async function(tid, pg, filter, extend) { + let url = this.input; + let html = await request(url); + let list = pdfa(html, 'li.col-6').map(it => ({ + title: pdfh(it, 'h3.f-14 a&&Text'), + pic_url: pdfh(it, 'img.lazy&&data-original'), + desc: pdfh(it, 'h3.f-14 a&&title').replace(/^[^(]*(/, '').replace(/)$/, ''), + url: pdfh(it, 'h3.f-14 a&&href'), + content: '' + })); + return setResult(list); + }, + + 二级: async function(ids) { + let url = this.input; + let html = await request(url); + let list = pdfa(html, '.content').map(content => { + let playList = pdfh(content, 'p'); + return '点我播放$push://' + pdfh(playList, 'a&&href'); + }); + return { + vod_name: pdfh(html, '[title]&&('), + vod_pic: pdfh(html, '[data-original]&&"'), + vod_content: '此为推送网盘规则', + vod_play_from: '短剧网', + vod_play_url: list.join('$$$') + }; + }, + + 搜索: async function () { + return await this.一级(); + } +}; \ No newline at end of file diff --git "a/spider/js/\347\253\213\346\222\255[\347\233\230].js" "b/spider/js/\347\253\213\346\222\255[\347\233\230].js" index a4177c14..6ce11df2 100644 --- "a/spider/js/\347\253\213\346\222\255[\347\233\230].js" +++ "b/spider/js/\347\253\213\346\222\255[\347\233\230].js" @@ -34,7 +34,7 @@ var rule = { limit: 90, double: false, play_parse: true, - class_parse: '.stui-header__menu li:gt(0):lt(7);a&&Text;a&&href;/(\\d+).html', + // class_parse: '.stui-header__menu li:gt(0):lt(7);a&&Text;a&&href;/(\\d+).html', 推荐: async function(tid, pg, filter, extend) { return this.一级(); diff --git "a/spider/js/\347\261\263\345\205\224\351\237\263\344\271\220[\345\220\254].js" "b/spider/js/\347\261\263\345\205\224\351\237\263\344\271\220[\345\220\254].js" index 6a2a6720..92050aca 100644 --- "a/spider/js/\347\261\263\345\205\224\351\237\263\344\271\220[\345\220\254].js" +++ "b/spider/js/\347\261\263\345\205\224\351\237\263\344\271\220[\345\220\254].js" @@ -9,51 +9,4 @@ }) */ -var rule = { - title: '米兔音乐', - host: 'https://api.qqmp3.vip', - url: '/api/fyclass', - searchUrl: '/api/songs.php?type=search&keyword=**', - class_name: '热门歌曲&新歌曲&随机歌曲', - class_url: 'songs.php&songs.php?type=new&songs.php?type=rand', - searchable: 2, - quickSearch: 0, - filterable: 0, - play_parse: true, - limit: 6, - double: true, - headers: { - 'User-Agent': 'Mozilla/5.0 (Linux; Android 12; V2284A Build/V417IR; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/101.0.4951.61 Safari/537.36', - 'Accept': '*/*', - 'Origin': 'https://www.qqmp3.vip', - 'referer': 'https://www.qqmp3.vip', - 'x-requested-with': 'com.mmbox.xbrowser', - 'Sec-Fetch-Site': 'same-site', - 'Sec-Fetch-Mode': 'cors', - 'Sec-Fetch-Dest': 'empty', - 'Accept-Language': 'zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7' - }, - 推荐: '*', - 一级: 'json:data;name;pic;artist;rid', - 二级: '*', - 搜索: 'json:data;name;pic;artist;rid', - lazy: async function() { - let ridMatch = this.input.match(/api\/([^/?]+)/); - if (!ridMatch) return this.input; - let rid = ridMatch[1]; - let api = 'https://api.qqmp3.vip/api/kw.php?rid=' + rid; - // console.log('解析接口:', api); - let json = await request(api); - let data = JSON.parse(json); - if (data.code === 200 && data.data?.url) { - return { - parse: 0, - url: data.data.url, - header: this.headers, - lrc: data.data.lrc || '', - playMode: 'repeat' - }; - } - return this.input; - }, -}; \ No newline at end of file +dmFyIHJ1bGUgPSB7CiAgdGl0bGU6ICfnsbPlhZTpn7PkuZAnLAogIGhvc3Q6ICdodHRwczovL2FwaS5xcW1wMy52aXAnLAogIHVybDogJy9hcGkvZnljbGFzcycsCiAgc2VhcmNoVXJsOiAnL2FwaS9zb25ncy5waHA/dHlwZT1zZWFyY2gma2V5d29yZD0qKicsCiAgY2xhc3NfbmFtZTogJ+eDremXqOatjOabsibmlrDmrYzmm7Im6ZqP5py65q2M5puyJywKICBjbGFzc191cmw6ICdzb25ncy5waHAmc29uZ3MucGhwP3R5cGU9bmV3JnNvbmdzLnBocD90eXBlPXJhbmQnLAogIHNlYXJjaGFibGU6IDIsCiAgcXVpY2tTZWFyY2g6IDAsCiAgZmlsdGVyYWJsZTogMCwKICBwbGF5X3BhcnNlOiB0cnVlLAogIGxpbWl0OiA2LAogIGRvdWJsZTogdHJ1ZSwKICBoZWFkZXJzOiB7CiAgICAnVXNlci1BZ2VudCc6ICdNb3ppbGxhLzUuMCAoTGludXg7IEFuZHJvaWQgMTI7IFYyMjg0QSBCdWlsZC9WNDE3SVI7IHd2KSBBcHBsZVdlYktpdC81MzcuMzYgKEtIVE1MLCBsaWtlIEdlY2tvKSBWZXJzaW9uLzQuMCBDaHJvbWUvMTAxLjAuNDk1MS42MSBTYWZhcmkvNTM3LjM2JywKICAgICdBY2NlcHQnOiAnKi8qJywKICAgICdPcmlnaW4nOiAnaHR0cHM6Ly93d3cucXFtcDMudmlwJywKICAgICdyZWZlcmVyJzogJ2h0dHBzOi8vd3d3LnFxbXAzLnZpcCcsCiAgICAneC1yZXF1ZXN0ZWQtd2l0aCc6ICdjb20ubW1ib3gueGJyb3dzZXInLAogICAgJ1NlYy1GZXRjaC1TaXRlJzogJ3NhbWUtc2l0ZScsCiAgICAnU2VjLUZldGNoLU1vZGUnOiAnY29ycycsCiAgICAnU2VjLUZldGNoLURlc3QnOiAnZW1wdHknLAogICAgJ0FjY2VwdC1MYW5ndWFnZSc6ICd6aC1DTix6aDtxPTAuOSxlbi1VUztxPTAuOCxlbjtxPTAuNycKICB9LAogIOaOqOiNkDogJyonLAogIOS4gOe6pzogJ2pzb246ZGF0YTtuYW1lO3BpYzthcnRpc3Q7cmlkJywKICDkuoznuqc6ICcqJywKICDmkJzntKI6ICdqc29uOmRhdGE7bmFtZTtwaWM7YXJ0aXN0O3JpZCcsCiAgbGF6eTogYXN5bmMgZnVuY3Rpb24oKSB7CiAgICBsZXQgcmlkTWF0Y2ggPSB0aGlzLmlucHV0Lm1hdGNoKC9hcGlcLyhbXi8/XSspLyk7CiAgICBpZiAoIXJpZE1hdGNoKSByZXR1cm4gdGhpcy5pbnB1dDsKICAgIGxldCByaWQgPSByaWRNYXRjaFsxXTsKICAgIGxldCBhcGkgPSAnaHR0cHM6Ly9hcGkucXFtcDMudmlwL2FwaS9rdy5waHA/cmlkPScgKyByaWQ7CiAgLy8gIGNvbnNvbGUubG9nKCfop6PmnpDmjqXlj6M6JywgYXBpKTsKICAgIGxldCBqc29uID0gYXdhaXQgcmVxdWVzdChhcGkpOwogICAgbGV0IGRhdGEgPSBKU09OLnBhcnNlKGpzb24pOwogICAgaWYgKGRhdGEuY29kZSA9PT0gMjAwICYmIGRhdGEuZGF0YT8udXJsKSB7CiAgICAgICAgcmV0dXJuIHsKICAgICAgICAgICAgcGFyc2U6IDAsCiAgICAgICAgICAgIHVybDogZGF0YS5kYXRhLnVybCwKICAgICAgICAgICAgaGVhZGVyOiB0aGlzLmhlYWRlcnMsCiAgICAgICAgICAgIGxyYzogZGF0YS5kYXRhLmxyYyB8fCAnJywKICAgICAgICAgICAgcGxheU1vZGU6ICdyZXBlYXQnCiAgICAgICAgfTsKICAgIH0KICAgIHJldHVybiB0aGlzLmlucHV0OwogIH0sCn07 \ No newline at end of file diff --git "a/spider/js/\350\200\220\347\234\213\347\202\271\346\222\255[\344\274\230].js" "b/spider/js/\350\200\220\347\234\213\347\202\271\346\222\255[\344\274\230].js" new file mode 100644 index 00000000..df7478a5 --- /dev/null +++ "b/spider/js/\350\200\220\347\234\213\347\202\271\346\222\255[\344\274\230].js" @@ -0,0 +1,14 @@ +/* +@header({ + searchable: 2, + filterable: 1, + quickSearch: 0, + title: '耐看影视', + author: 'EylinSir', + '类型': '影视', + logo: 'https://nkvod.org/upload/site/20241223-1/7c00a9d60fffa62f46be199e52d6cc85.png', + lang: 'ds' +}) +*/ + +dmFyIHJ1bGUgPSB7CiAgICDnsbvlnos6ICflvbHop4YnLAogICAgYXV0aG9yOiAnRXlsaW5TaXInLAogICAgdGl0bGU6ICfogJDnnIvlvbHop4YnLAogICAgaG9zdDogJ2h0dHBzOi8vbmt2b2Qub3JnJywKICAgIHVybDogJy9zaG93L2Z5Y2xhc3MtZnlmaWx0ZXItZnlwYWdlLmh0bWwnLAogICAgc2VhcmNoVXJsOiAnL25rLy0tLS0tLS0tLS0tLS0uaHRtbD93ZD0qKicsCiAgICBsb2dvOiAnaHR0cHM6Ly9ua3ZvZC5vcmcvdXBsb2FkL3NpdGUvMjAyNDEyMjMtMS83YzAwYTlkNjBmZmZhNjJmNDZiZTE5OWU1MmQ2Y2M4NS5wbmcnLAogICAgc2VhcmNoYWJsZTogMiwKICAgIHF1aWNrU2VhcmNoOiAwLAogICAgZmlsdGVyYWJsZTogMSwKICAgIHBsYXlfcGFyc2U6IHRydWUsCiAgICBzZWFyY2hDb29raWU6ICcnLAogICAgY2xhc3NfbmFtZTogJ+eUteW9sSbliafpm4Ym5Yqo5ryrJue7vOiJuicsCiAgICBjbGFzc191cmw6ICcxJjImNCYzJywKICAgIGhlYWRlcnM6IHsKICAgICAgICAnVXNlci1BZ2VudCc6ICdNb3ppbGxhLzUuMCAoTGludXg7IEFuZHJvaWQgMTI7IEFMTi1BTDAwIEJ1aWxkL0hVQVdFSUFMTi1BTDAwOyB3dikgQXBwbGVXZWJLaXQvNTM3LjM2IChLSFRNTCwgbGlrZSBHZWNrbykgVmVyc2lvbi80LjAgQ2hyb21lLzExNC4wLjU3MzUuMTk2IE1vYmlsZSBTYWZhcmkvNTM3LjM2JywKICAgICAgICAnUmVmZXJlcic6ICdodHRwczovL25rdm9kLm9yZy8nCiAgICB9LAoKICAgIHVwZGF0ZUNvb2tpZTogZnVuY3Rpb24oYykgewogICAgICAgIGlmICghYykgcmV0dXJuOwogICAgICAgIGxldCBjQXJyID0gQXJyYXkuaXNBcnJheShjKSA/IGMgOiBbY107CiAgICAgICAgbGV0IGRpY3QgPSB7fTsKICAgICAgICBpZiAodGhpcy5zZWFyY2hDb29raWUpIHsKICAgICAgICAgICAgdGhpcy5zZWFyY2hDb29raWUuc3BsaXQoJzsnKS5mb3JFYWNoKGkgPT4gewogICAgICAgICAgICAgICAgbGV0IHAgPSBpLnNwbGl0KCc9Jyk7CiAgICAgICAgICAgICAgICBpZiAocC5sZW5ndGggPj0gMikgZGljdFtwWzBdLnRyaW0oKV0gPSBwLnNsaWNlKDEpLmpvaW4oJz0nKTsKICAgICAgICAgICAgfSk7CiAgICAgICAgfQogICAgICAgIGNBcnIuZm9yRWFjaChpID0+IHsKICAgICAgICAgICAgbGV0IGt2ID0gaS5zcGxpdCgnOycpWzBdLnRyaW0oKTsKICAgICAgICAgICAgbGV0IHAgPSBrdi5zcGxpdCgnPScpOwogICAgICAgICAgICBpZiAocC5sZW5ndGggPj0gMikgZGljdFtwWzBdLnRyaW0oKV0gPSBwLnNsaWNlKDEpLmpvaW4oJz0nKTsKICAgICAgICB9KTsKICAgICAgICBsZXQgcmVzID0gW107CiAgICAgICAgZm9yIChsZXQgayBpbiBkaWN0KSByZXMucHVzaChrICsgJz0nICsgZGljdFtrXSk7CiAgICAgICAgdGhpcy5zZWFyY2hDb29raWUgPSByZXMuam9pbignOycpOwogICAgfSwKCiAgICBDcnlwdG9Ub29sOiB7CiAgICAgICAgZ2V0cmFuZG9tOiBmdW5jdGlvbihiKSB7CiAgICAgICAgICAgIHRyeSB7CiAgICAgICAgICAgICAgICBsZXQgc3RyaW5nID0gYi5zdWJzdHJpbmcoMTApOwogICAgICAgICAgICAgICAgbGV0IHdvcmRzID0gQ3J5cHRvSlMuZW5jLkJhc2U2NC5wYXJzZShzdHJpbmcpOwogICAgICAgICAgICAgICAgbGV0IHN1YnN0ciA9IENyeXB0b0pTLmVuYy5MYXRpbjEuc3RyaW5naWZ5KHdvcmRzKTsKICAgICAgICAgICAgICAgIGlmICghc3Vic3RyKSByZXR1cm4gIiI7CiAgICAgICAgICAgICAgICBsZXQgZGF0YTIgPSBzdWJzdHIuc3Vic3RyaW5nKDEwKS5yZXBsYWNlKCdfbmFua2UnLCAnJyk7CiAgICAgICAgICAgICAgICBsZXQgZGF0YTMgPSBkYXRhMi5zbGljZSgwLCAyMCkgKyBkYXRhMi5zbGljZSgyMSk7CiAgICAgICAgICAgICAgICBsZXQgaGV4U3RyID0gZGF0YTMucmVwbGFjZSgvW14wLTlhLWZBLUZdL2csICcnKTsKICAgICAgICAgICAgICAgIHJldHVybiB0aGlzLmhleERlY29kZUFuZEZpbHRlcihoZXhTdHIpOwogICAgICAgICAgICB9IGNhdGNoIChlcnJvcikgeyByZXR1cm4gIiI7IH0KICAgICAgICB9LAogICAgICAgIGhleERlY29kZUFuZEZpbHRlcjogZnVuY3Rpb24oaGV4U3RyKSB7CiAgICAgICAgICAgIHRyeSB7CiAgICAgICAgICAgICAgICBsZXQgcHVyZUhleCA9IGhleFN0ci5yZXBsYWNlKC9bXjAtOWEtZkEtRl0vZywgJycpOwogICAgICAgICAgICAgICAgaWYgKHB1cmVIZXgubGVuZ3RoICUgMiAhPT0gMCkgcHVyZUhleCArPSAnMCc7CiAgICAgICAgICAgICAgICBsZXQgZGVjb2RlZCA9ICcnOwogICAgICAgICAgICAgICAgZm9yIChsZXQgaSA9IDA7IGkgPCBwdXJlSGV4Lmxlbmd0aDsgaSArPSAyKSB7CiAgICAgICAgICAgICAgICAgICAgbGV0IGNoYXIgPSBTdHJpbmcuZnJvbUNoYXJDb2RlKHBhcnNlSW50KHB1cmVIZXguc3Vic3RyKGksIDIpLCAxNikpOwogICAgICAgICAgICAgICAgICAgIGlmICgvW2EtekEtWjAtOTpcL1wuXC1cP1wmPVwlX35dLy50ZXN0KGNoYXIpKSBkZWNvZGVkICs9IGNoYXI7CiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICBsZXQgdXJsTWF0Y2ggPSBkZWNvZGVkLm1hdGNoKC9odHRwcz86XC9cL1teXHNdKy8pOwogICAgICAgICAgICAgICAgcmV0dXJuIHVybE1hdGNoID8gdXJsTWF0Y2hbMF0gOiBkZWNvZGVkLnRyaW0oKTsKICAgICAgICAgICAgfSBjYXRjaCAoZSkgeyByZXR1cm4gIiI7IH0KICAgICAgICB9LAogICAgICAgIGFycjJoZXg6IGZ1bmN0aW9uKGFycikgewogICAgICAgICAgICByZXR1cm4gYXJyLm1hcChmdW5jdGlvbihiKSB7CiAgICAgICAgICAgICAgICByZXR1cm4gKCcwJyArIChiICYgMHhGRikudG9TdHJpbmcoMTYpKS5zbGljZSgtMik7CiAgICAgICAgICAgIH0pLmpvaW4oJycpOwogICAgICAgIH0sCiAgICAgICAgZGVjcnlwdERhdGE6IGZ1bmN0aW9uKGhleCwga2V5QXJyLCBpdkFycikgewogICAgICAgICAgICB0cnkgewogICAgICAgICAgICAgICAgbGV0IGtleSA9IENyeXB0b0pTLmVuYy5IZXgucGFyc2UodGhpcy5hcnIyaGV4KGtleUFycikpOwogICAgICAgICAgICAgICAgbGV0IGl2ID0gQ3J5cHRvSlMuZW5jLkhleC5wYXJzZSh0aGlzLmFycjJoZXgoaXZBcnIpKTsKICAgICAgICAgICAgICAgIGxldCBzcmMgPSBDcnlwdG9KUy5lbmMuSGV4LnBhcnNlKGhleCk7CiAgICAgICAgICAgICAgICBsZXQgbW9kZXMgPSBbQ3J5cHRvSlMubW9kZS5DQkMsIENyeXB0b0pTLm1vZGUuRUNCLCBDcnlwdG9KUy5tb2RlLk9GQiwgQ3J5cHRvSlMubW9kZS5DRkIsIENyeXB0b0pTLm1vZGUuQ1RSXTsKICAgICAgICAgICAgICAgIGZvciAobGV0IGkgPSAwOyBpIDwgbW9kZXMubGVuZ3RoOyBpKyspIHsKICAgICAgICAgICAgICAgICAgICB0cnkgewogICAgICAgICAgICAgICAgICAgICAgICBsZXQgcGFyYW0gPSB7IG1vZGU6IG1vZGVzW2ldLCBwYWRkaW5nOiBDcnlwdG9KUy5wYWQuUGtjczcgfTsKICAgICAgICAgICAgICAgICAgICAgICAgaWYgKG1vZGVzW2ldICE9PSBDcnlwdG9KUy5tb2RlLkVDQikgcGFyYW0uaXYgPSBpdjsKICAgICAgICAgICAgICAgICAgICAgICAgbGV0IGRlYyA9IENyeXB0b0pTLkFFUy5kZWNyeXB0KHtjaXBoZXJ0ZXh0OiBzcmN9LCBrZXksIHBhcmFtKTsKICAgICAgICAgICAgICAgICAgICAgICAgbGV0IHJlcyA9IGRlYy50b1N0cmluZyhDcnlwdG9KUy5lbmMuVXRmOCk7CiAgICAgICAgICAgICAgICAgICAgICAgIGlmIChyZXMpIHJldHVybiByZXM7CiAgICAgICAgICAgICAgICAgICAgfSBjYXRjaCAoZSkge30KICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgIHJldHVybiBudWxsOwogICAgICAgICAgICB9IGNhdGNoIChlKSB7IHJldHVybiBudWxsOyB9CiAgICAgICAgfQogICAgfSwKCiAgICByZXF1ZXN0OiBhc3luYyBmdW5jdGlvbih1cmwsIG9wdCkgewogICAgICAgIGxldCBvcHRzID0geyBtZXRob2Q6IG9wdD8ubWV0aG9kIHx8ICdHRVQnLCBoZWFkZXJzOiBPYmplY3QuYXNzaWduKHt9LCB0aGlzLmhlYWRlcnMsIG9wdD8uaGVhZGVycyB8fCB7fSksIGRhdGE6IG9wdD8uYm9keSB9OwogICAgICAgIGlmICh0aGlzLnNlYXJjaENvb2tpZSkgb3B0cy5oZWFkZXJzLkNvb2tpZSA9IHRoaXMuc2VhcmNoQ29va2llOwogICAgICAgIGxldCByZXMgPSBhd2FpdCByZXEodXJsLCBvcHRzKTsKICAgICAgICBpZiAocmVzLmhlYWRlcnNbJ3NldC1jb29raWUnXSkgewogICAgICAgICAgICB0aGlzLnVwZGF0ZUNvb2tpZShyZXMuaGVhZGVyc1snc2V0LWNvb2tpZSddKTsKICAgICAgICB9CiAgICAgICAgaWYgKHJlcy5jb250ZW50LmluY2x1ZGVzKCfns7vnu5/lronlhajpqozor4EnKSB8fCByZXMuY29udGVudC5pbmNsdWRlcygn6K+36L6T5YWl6aqM6K+B56CBJykgfHwgcmVzLmNvbnRlbnQuaW5jbHVkZXMoJ3ZlcmlmeV9jaGVjaycpKSB7CiAgICAgICAgICAgIGlmIChhd2FpdCB0aGlzLmZldGNoQ2sodXJsKSkgewogICAgICAgICAgICAgICAgb3B0cy5oZWFkZXJzLkNvb2tpZSA9IHRoaXMuc2VhcmNoQ29va2llOwogICAgICAgICAgICAgICAgaWYgKCFvcHRzLmhlYWRlcnMuUmVmZXJlcikgb3B0cy5oZWFkZXJzLlJlZmVyZXIgPSB1cmw7CiAgICAgICAgICAgICAgICByZXMgPSBhd2FpdCByZXEodXJsLCBvcHRzKTsKICAgICAgICAgICAgICAgIGlmIChyZXMuaGVhZGVyc1snc2V0LWNvb2tpZSddKSB0aGlzLnVwZGF0ZUNvb2tpZShyZXMuaGVhZGVyc1snc2V0LWNvb2tpZSddKTsKICAgICAgICAgICAgfQogICAgICAgIH0KICAgICAgICByZXR1cm4gcmVzLmNvbnRlbnQ7CiAgICB9LAoKICAgIGZldGNoQ2s6IGFzeW5jIGZ1bmN0aW9uKHJlZikgewogICAgICAgIGZvciAobGV0IGkgPSAwOyBpIDwgMzsgaSsrKSB7CiAgICAgICAgICAgIHRyeSB7CiAgICAgICAgICAgICAgICBsZXQgeXptID0gdGhpcy5ob3N0ICsgIi9pbmRleC5waHAvdmVyaWZ5L2luZGV4Lmh0bWw/IiArIE1hdGgucmFuZG9tKCk7CiAgICAgICAgICAgICAgICBsZXQgaCA9IHsgJ1VzZXItQWdlbnQnOiB0aGlzLmhlYWRlcnNbJ1VzZXItQWdlbnQnXSwgJ1JlZmVyZXInOiByZWYgfTsKICAgICAgICAgICAgICAgIGlmICh0aGlzLnNlYXJjaENvb2tpZSkgaC5Db29raWUgPSB0aGlzLnNlYXJjaENvb2tpZTsKICAgICAgICAgICAgICAgIGxldCByZXMgPSBhd2FpdCByZXEoeXptLCB7IGhlYWRlcnM6IGgsIGJ1ZmZlcjogMiB9KTsKICAgICAgICAgICAgICAgIGlmIChyZXMuaGVhZGVyc1snc2V0LWNvb2tpZSddKSB0aGlzLnVwZGF0ZUNvb2tpZShyZXMuaGVhZGVyc1snc2V0LWNvb2tpZSddKTsKICAgICAgICAgICAgICAgIGxldCBjb2RlID0gJyc7CiAgICAgICAgICAgICAgICBpZiAodHlwZW9mIG9jciA9PT0gJ2Z1bmN0aW9uJykgewogICAgICAgICAgICAgICAgICAgIHRyeSB7IGNvZGUgPSBhd2FpdCBvY3IoeXptLCByZXMuY29udGVudCk7IH0gY2F0Y2ggKGUpIHt9CiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICBpZiAoIWNvZGUpIHsKICAgICAgICAgICAgICAgICAgICBsZXQgciA9IGF3YWl0IHJlcSgiaHR0cHM6Ly9hcGkubm4uY2kvb2NyL2I2NC90ZXh0IiwgeyBtZXRob2Q6ICdQT1NUJywgaGVhZGVyczogeydDb250ZW50LVR5cGUnOid0ZXh0L3BsYWluJ30sIGJvZHk6IHJlcy5jb250ZW50IH0pOwogICAgICAgICAgICAgICAgICAgIGNvZGUgPSByLmNvbnRlbnQ7CiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICBjb2RlID0gY29kZS50cmltKCkucmVwbGFjZSgvXHMrL2csICcnKTsKICAgICAgICAgICAgICAgIGlmICghY29kZSkgY29udGludWU7CiAgICAgICAgICAgICAgICBsZXQgdlJlcyA9IGF3YWl0IHJlcSh0aGlzLmhvc3QgKyAiL2luZGV4LnBocC9hamF4L3ZlcmlmeV9jaGVjaz90eXBlPXNlYXJjaCZ2ZXJpZnk9IiArIGNvZGUsIHsKICAgICAgICAgICAgICAgICAgICBtZXRob2Q6ICdHRVQnLAogICAgICAgICAgICAgICAgICAgIGhlYWRlcnM6IHsgJ1VzZXItQWdlbnQnOiB0aGlzLmhlYWRlcnNbJ1VzZXItQWdlbnQnXSwgJ1JlZmVyZXInOiByZWYsICdYLVJlcXVlc3RlZC1XaXRoJzogJ1hNTEh0dHBSZXF1ZXN0JywgJ0Nvb2tpZSc6IHRoaXMuc2VhcmNoQ29va2llIH0KICAgICAgICAgICAgICAgIH0pOwogICAgICAgICAgICAgICAgaWYgKHZSZXMuY29udGVudC5pbmNsdWRlcygnImNvZGUiOjEnKSkgewogICAgICAgICAgICAgICAgICAgIGlmICh2UmVzLmhlYWRlcnNbJ3NldC1jb29raWUnXSkgdGhpcy51cGRhdGVDb29raWUodlJlcy5oZWFkZXJzWydzZXQtY29va2llJ10pOwogICAgICAgICAgICAgICAgICAgIHJldHVybiB0cnVlOwogICAgICAgICAgICAgICAgfQogICAgICAgICAgICB9IGNhdGNoIChlKSB7fQogICAgICAgIH0KICAgICAgICByZXR1cm4gZmFsc2U7CiAgICB9LAoKICAgIOmihOWkhOeQhjogYXN5bmMgZnVuY3Rpb24oKSB7CiAgICAgICAgbGV0IGZpbHRlcnMgPSB7CiAgICAgICAgICAgICIxIjpbeyJrZXkiOiJjbGFzcyIsIm5hbWUiOiLliafmg4UiLCJ2YWx1ZSI6W3sibiI6IuWFqOmDqCIsInYiOiIifSx7Im4iOiLllpzliaciLCJ2Ijoi5Zac5YmnIn0seyJuIjoi54ix5oOFIiwidiI6IueIseaDhSJ9LHsibiI6IuaBkOaAliIsInYiOiLmgZDmgJYifSx7Im4iOiLliqjkvZwiLCJ2Ijoi5Yqo5L2cIn0seyJuIjoi56eR5bm7IiwidiI6IuenkeW5uyJ9LHsibiI6IuWJp+aDhSIsInYiOiLliafmg4UifSx7Im4iOiLmiJjkuokiLCJ2Ijoi5oiY5LqJIn0seyJuIjoi6K2m5YyqIiwidiI6IuitpuWMqiJ9LHsibiI6IueKr+e9qiIsInYiOiLniq/nvaoifSx7Im4iOiLliqjnlLsiLCJ2Ijoi5Yqo55S7In0seyJuIjoi5aWH5bm7IiwidiI6IuWlh+W5uyJ9LHsibiI6IuatpuS+oCIsInYiOiLmrabkvqAifSx7Im4iOiLlhpLpmakiLCJ2Ijoi5YaS6ZmpIn0seyJuIjoi5p6q5oiYIiwidiI6IuaequaImCJ9LHsibiI6IuaCrOeWkSIsInYiOiLmgqznlpEifSx7Im4iOiLmg4rmgpoiLCJ2Ijoi5oOK5oKaIn0seyJuIjoi57uP5YW4IiwidiI6Iue7j+WFuCJ9LHsibiI6IumdkuaYpSIsInYiOiLpnZLmmKUifSx7Im4iOiLmlofoiboiLCJ2Ijoi5paH6Im6In0seyJuIjoi5b6u55S15b2xIiwidiI6IuW+rueUteW9sSJ9LHsibiI6IuWPpOijhSIsInYiOiLlj6Too4UifSx7Im4iOiLljoblj7IiLCJ2Ijoi5Y6G5Y+yIn0seyJuIjoi6L+Q5YqoIiwidiI6Iui/kOWKqCJ9LHsibiI6IuWGnOadkSIsInYiOiLlhpzmnZEifSx7Im4iOiLlhL/nq6UiLCJ2Ijoi5YS/56ulIn0seyJuIjoi572R57uc55S15b2xIiwidiI6Iue9kee7nOeUteW9sSJ9XX0seyJrZXkiOiJhcmVhIiwibmFtZSI6IuWcsOWMuiIsInZhbHVlIjpbeyJuIjoi5YWo6YOoIiwidiI6IiJ9LHsibiI6IuWkp+mZhiIsInYiOiLlpKfpmYYifSx7Im4iOiLpppnmuK8iLCJ2Ijoi6aaZ5rivIn0seyJuIjoi5Y+w5rm+IiwidiI6IuWPsOa5viJ9LHsibiI6Iue+juWbvSIsInYiOiLnvo7lm70ifSx7Im4iOiLms5Xlm70iLCJ2Ijoi5rOV5Zu9In0seyJuIjoi6Iux5Zu9IiwidiI6IuiLseWbvSJ9LHsibiI6IuaXpeacrCIsInYiOiLml6XmnKwifSx7Im4iOiLpn6nlm70iLCJ2Ijoi6Z+p5Zu9In0seyJuIjoi5b635Zu9IiwidiI6IuW+t+WbvSJ9LHsibiI6IuazsOWbvSIsInYiOiLms7Dlm70ifSx7Im4iOiLljbDluqYiLCJ2Ijoi5Y2w5bqmIn0seyJuIjoi5oSP5aSn5YipIiwidiI6IuaEj+Wkp+WIqSJ9LHsibiI6Iuilv+ePreeJmSIsInYiOiLopb/nj63niZkifSx7Im4iOiLliqDmi7/lpKciLCJ2Ijoi5Yqg5ou/5aSnIn0seyJuIjoi5YW25a6DIiwidiI6IuWFtuWugyJ9XX0seyJrZXkiOiJ5ZWFyIiwibmFtZSI6IuW5tOS7vSIsInZhbHVlIjpbeyJuIjoi5YWo6YOoIiwidiI6IiJ9LHsibiI6IjIwMjYiLCJ2IjoiMjAyNiJ9LHsibiI6IjIwMjUiLCJ2IjoiMjAyNSJ9LHsibiI6IjIwMjQiLCJ2IjoiMjAyNCJ9LHsibiI6IjIwMjMiLCJ2IjoiMjAyMyJ9LHsibiI6IjIwMjIiLCJ2IjoiMjAyMiJ9LHsibiI6IjIwMjEiLCJ2IjoiMjAyMSJ9LHsibiI6IjIwMjAiLCJ2IjoiMjAyMCJ9LHsibiI6IjIwMTkiLCJ2IjoiMjAxOSJ9LHsibiI6IjIwMTgiLCJ2IjoiMjAxOCJ9LHsibiI6IjIwMTciLCJ2IjoiMjAxNyJ9LHsibiI6IjIwMTYiLCJ2IjoiMjAxNiJ9LHsibiI6IjIwMTUiLCJ2IjoiMjAxNSJ9LHsibiI6IjIwMTQiLCJ2IjoiMjAxNCJ9LHsibiI6IjIwMTMiLCJ2IjoiMjAxMyJ9LHsibiI6IjIwMTIiLCJ2IjoiMjAxMiJ9LHsibiI6IjIwMTEiLCJ2IjoiMjAxMSJ9LHsibiI6IjIwMTAiLCJ2IjoiMjAxMCJ9XX0seyJrZXkiOiJsYW5nIiwibmFtZSI6IuivreiogCIsInZhbHVlIjpbeyJuIjoi5YWo6YOoIiwidiI6IiJ9LHsibiI6IuWbveivrSIsInYiOiLlm73or60ifSx7Im4iOiLoi7Hor60iLCJ2Ijoi6Iux6K+tIn0seyJuIjoi57Kk6K+tIiwidiI6IueypOivrSJ9LHsibiI6IumXveWNl+ivrSIsInYiOiLpl73ljZfor60ifSx7Im4iOiLpn6nor60iLCJ2Ijoi6Z+p6K+tIn0seyJuIjoi5pel6K+tIiwidiI6IuaXpeivrSJ9LHsibiI6IuazleivrSIsInYiOiLms5Xor60ifSx7Im4iOiLlvrfor60iLCJ2Ijoi5b636K+tIn0seyJuIjoi5YW25a6DIiwidiI6IuWFtuWugyJ9XX0seyJrZXkiOiJsZXR0ZXIiLCJuYW1lIjoi5a2X5q+NIiwidmFsdWUiOlt7Im4iOiLlrZfmr40iLCJ2IjoiIn0seyJuIjoiQSIsInYiOiJBIn0seyJuIjoiQiIsInYiOiJCIn0seyJuIjoiQyIsInYiOiJDIn0seyJuIjoiRCIsInYiOiJEIn0seyJuIjoiRSIsInYiOiJFIn0seyJuIjoiRiIsInYiOiJGIn0seyJuIjoiRyIsInYiOiJHIn0seyJuIjoiSCIsInYiOiJIIn0seyJuIjoiSSIsInYiOiJJIn0seyJuIjoiSiIsInYiOiJKIn0seyJuIjoiSyIsInYiOiJLIn0seyJuIjoiTCIsInYiOiJMIn0seyJuIjoiTSIsInYiOiJNIn0seyJuIjoiTiIsInYiOiJOIn0seyJuIjoiTyIsInYiOiJPIn0seyJuIjoiUCIsInYiOiJQIn0seyJuIjoiUSIsInYiOiJRIn0seyJuIjoiUiIsInYiOiJSIn0seyJuIjoiUyIsInYiOiJTIn0seyJuIjoiVCIsInYiOiJUIn0seyJuIjoiVSIsInYiOiJVIn0seyJuIjoiViIsInYiOiJWIn0seyJuIjoiVyIsInYiOiJXIn0seyJuIjoiWCIsInYiOiJYIn0seyJuIjoiWSIsInYiOiJZIn0seyJuIjoiWiIsInYiOiJaIn0seyJuIjoiMC05IiwidiI6IjAtOSJ9XX0seyJrZXkiOiJieSIsIm5hbWUiOiLmjpLluo8iLCJ2YWx1ZSI6W3sibiI6IuaXtumXtOaOkuW6jyIsInYiOiJ0aW1lIn0seyJuIjoi5Lq65rCU5o6S5bqPIiwidiI6ImhpdHMifSx7Im4iOiLor4TliIbmjpLluo8iLCJ2Ijoic2NvcmUifV19XSwKICAgICAgICAgICAgIjIiOlt7ImtleSI6ImNsYXNzIiwibmFtZSI6IuWJp+aDhSIsInZhbHVlIjpbeyJuIjoi5YWo6YOoIiwidiI6IiJ9LHsibiI6IuWPpOijhSIsInYiOiLlj6Too4UifSx7Im4iOiLmiJjkuokiLCJ2Ijoi5oiY5LqJIn0seyJuIjoi6Z2S5pil5YG25YOPIiwidiI6IumdkuaYpeWBtuWDjyJ9LHsibiI6IuWWnOWJpyIsInYiOiLllpzliacifSx7Im4iOiLliqjkvZwiLCJ2Ijoi5Yqo5L2cIn0seyJuIjoi5aWH5bm7IiwidiI6IuWlh+W5uyJ9LHsibiI6IuWJp+aDhSIsInYiOiLliafmg4UifSx7Im4iOiLljoblj7IiLCJ2Ijoi5Y6G5Y+yIn0seyJuIjoi57uP5YW4IiwidiI6Iue7j+WFuCJ9LHsibiI6IuS5oeadkSIsInYiOiLkuaHmnZEifSx7Im4iOiLmg4Xmma8iLCJ2Ijoi5oOF5pmvIn0seyJuIjoi5ZWG5oiYIiwidiI6IuWVhuaImCJ9LHsibiI6Iue9keWJpyIsInYiOiLnvZHliacifSx7Im4iOiLlhbbku5YiLCJ2Ijoi5YW25LuWIn1dfSx7ImtleSI6ImFyZWEiLCJuYW1lIjoi5Zyw5Yy6IiwidmFsdWUiOlt7Im4iOiLlhajpg6giLCJ2IjoiIn0seyJuIjoi5YaF5ZywIiwidiI6IuWGheWcsCJ9LHsibiI6IumfqeWbvSIsInYiOiLpn6nlm70ifSx7Im4iOiLpppnmuK8iLCJ2Ijoi6aaZ5rivIn0seyJuIjoi5Y+w5rm+IiwidiI6IuWPsOa5viJ9LHsibiI6IuaXpeacrCIsInYiOiLml6XmnKwifSx7Im4iOiLnvo7lm70iLCJ2Ijoi576O5Zu9In0seyJuIjoi5rOw5Zu9IiwidiI6IuazsOWbvSJ9LHsibiI6IuiLseWbvSIsInYiOiLoi7Hlm70ifSx7Im4iOiLmlrDliqDlnaEiLCJ2Ijoi5paw5Yqg5Z2hIn0seyJuIjoi5YW25LuWIiwidiI6IuWFtuS7liJ9XX0seyJrZXkiOiJsYW5nIiwibmFtZSI6IuivreiogCIsInZhbHVlIjpbeyJuIjoi5YWo6YOoIiwidiI6IiJ9LHsibiI6IuWbveivrSIsInYiOiLlm73or60ifSx7Im4iOiLoi7Hor60iLCJ2Ijoi6Iux6K+tIn0seyJuIjoi57Kk6K+tIiwidiI6IueypOivrSJ9LHsibiI6IumXveWNl+ivrSIsInYiOiLpl73ljZfor60ifSx7Im4iOiLpn6nor60iLCJ2Ijoi6Z+p6K+tIn0seyJuIjoi5pel6K+tIiwidiI6IuaXpeivrSJ9LHsibiI6IuWFtuWugyIsInYiOiLlhbblroMifV19LHsia2V5IjoieWVhciIsIm5hbWUiOiLlubTku70iLCJ2YWx1ZSI6W3sibiI6IuWFqOmDqCIsInYiOiIifSx7Im4iOiIyMDI2IiwidiI6IjIwMjYifSx7Im4iOiIyMDI1IiwidiI6IjIwMjUifSx7Im4iOiIyMDI0IiwidiI6IjIwMjQifSx7Im4iOiIyMDIzIiwidiI6IjIwMjMifSx7Im4iOiIyMDIyIiwidiI6IjIwMjIifSx7Im4iOiIyMDIxIiwidiI6IjIwMjEifSx7Im4iOiIyMDIwIiwidiI6IjIwMjAifSx7Im4iOiIyMDE5IiwidiI6IjIwMTkifSx7Im4iOiIyMDE4IiwidiI6IjIwMTgifSx7Im4iOiIyMDE3IiwidiI6IjIwMTcifSx7Im4iOiIyMDE2IiwidiI6IjIwMTYifSx7Im4iOiIyMDE1IiwidiI6IjIwMTUifSx7Im4iOiIyMDE0IiwidiI6IjIwMTQifSx7Im4iOiIyMDEzIiwidiI6IjIwMTMifSx7Im4iOiIyMDEyIiwidiI6IjIwMTIifSx7Im4iOiIyMDExIiwidiI6IjIwMTEifSx7Im4iOiIyMDEwIiwidiI6IjIwMTAifSx7Im4iOiIyMDA5IiwidiI6IjIwMDkifSx7Im4iOiIyMDA4IiwidiI6IjIwMDgifSx7Im4iOiIyMDA2IiwidiI6IjIwMDYifSx7Im4iOiIyMDA1IiwidiI6IjIwMDUifSx7Im4iOiIyMDA0IiwidiI6IjIwMDQifV19LHsia2V5IjoibGV0dGVyIiwibmFtZSI6IuWtl+avjSIsInZhbHVlIjpbeyJuIjoi5a2X5q+NIiwidiI6IiJ9LHsibiI6IkEiLCJ2IjoiQSJ9LHsibiI6IkIiLCJ2IjoiQiJ9LHsibiI6IkMiLCJ2IjoiQyJ9LHsibiI6IkQiLCJ2IjoiRCJ9LHsibiI6IkUiLCJ2IjoiRSJ9LHsibiI6IkYiLCJ2IjoiRiJ9LHsibiI6IkciLCJ2IjoiRyJ9LHsibiI6IkgiLCJ2IjoiSCJ9LHsibiI6IkkiLCJ2IjoiSSJ9LHsibiI6IkoiLCJ2IjoiSiJ9LHsibiI6IksiLCJ2IjoiSyJ9LHsibiI6IkwiLCJ2IjoiTCJ9LHsibiI6Ik0iLCJ2IjoiTSJ9LHsibiI6Ik4iLCJ2IjoiTiJ9LHsibiI6Ik8iLCJ2IjoiTyJ9LHsibiI6IlAiLCJ2IjoiUCJ9LHsibiI6IlEiLCJ2IjoiUSJ9LHsibiI6IlIiLCJ2IjoiUiJ9LHsibiI6IlMiLCJ2IjoiUyJ9LHsibiI6IlQiLCJ2IjoiVCJ9LHsibiI6IlUiLCJ2IjoiVSJ9LHsibiI6IlYiLCJ2IjoiViJ9LHsibiI6IlciLCJ2IjoiVyJ9LHsibiI6IlgiLCJ2IjoiWCJ9LHsibiI6IlkiLCJ2IjoiWSJ9LHsibiI6IloiLCJ2IjoiWiJ9LHsibiI6IjAtOSIsInYiOiIwLTkifV19LHsia2V5IjoiYnkiLCJuYW1lIjoi5o6S5bqPIiwidmFsdWUiOlt7Im4iOiLml7bpl7TmjpLluo8iLCJ2IjoidGltZSJ9LHsibiI6IuS6uuawlOaOkuW6jyIsInYiOiJoaXRzIn0seyJuIjoi6K+E5YiG5o6S5bqPIiwidiI6InNjb3JlIn1dfV0sCiAgICAgICAgICAgICIzIjpbeyJrZXkiOiJjbGFzcyIsIm5hbWUiOiLliafmg4UiLCJ2YWx1ZSI6W3sibiI6IuWFqOmDqCIsInYiOiIifSx7Im4iOiLpgInnp4AiLCJ2Ijoi6YCJ56eAIn0seyJuIjoi5oOF5oSfIiwidiI6IuaDheaEnyJ9LHsibiI6Iuiuv+iwiCIsInYiOiLorr/osIgifSx7Im4iOiLmkq3miqUiLCJ2Ijoi5pKt5oqlIn0seyJuIjoi5peF5ri4IiwidiI6IuaXhea4uCJ9LHsibiI6Iumfs+S5kCIsInYiOiLpn7PkuZAifSx7Im4iOiLnvo7po58iLCJ2Ijoi576O6aOfIn0seyJuIjoi57qq5a6eIiwidiI6Iue6quWuniJ9LHsibiI6IuabsuiJuiIsInYiOiLmm7LoiboifSx7Im4iOiLnlJ/mtLsiLCJ2Ijoi55Sf5rS7In0seyJuIjoi5ri45oiP5LqS5YqoIiwidiI6Iua4uOaIj+S6kuWKqCJ9LHsibiI6Iui0oue7jyIsInYiOiLotKLnu48ifSx7Im4iOiLmsYLogYwiLCJ2Ijoi5rGC6IGMIn1dfSx7ImtleSI6ImFyZWEiLCJuYW1lIjoi5Zyw5Yy6IiwidmFsdWUiOlt7Im4iOiLlhajpg6giLCJ2IjoiIn0seyJuIjoi5YaF5ZywIiwidiI6IuWGheWcsCJ9LHsibiI6Iua4r+WPsCIsInYiOiLmuK/lj7AifSx7Im4iOiLml6Xpn6kiLCJ2Ijoi5pel6Z+pIn0seyJuIjoi5qyn576OIiwidiI6Iuasp+e+jiJ9XX0seyJrZXkiOiJsYW5nIiwibmFtZSI6IuivreiogCIsInZhbHVlIjpbeyJuIjoi5YWo6YOoIiwidiI6IiJ9LHsibiI6IuWbveivrSIsInYiOiLlm73or60ifSx7Im4iOiLoi7Hor60iLCJ2Ijoi6Iux6K+tIn0seyJuIjoi57Kk6K+tIiwidiI6IueypOivrSJ9LHsibiI6IumXveWNl+ivrSIsInYiOiLpl73ljZfor60ifSx7Im4iOiLpn6nor60iLCJ2Ijoi6Z+p6K+tIn0seyJuIjoi5pel6K+tIiwidiI6IuaXpeivrSJ9LHsibiI6IuWFtuWugyIsInYiOiLlhbblroMifV19LHsia2V5IjoieWVhciIsIm5hbWUiOiLlubTku70iLCJ2YWx1ZSI6W3sibiI6IuWFqOmDqCIsInYiOiIifSx7Im4iOiIyMDI2IiwidiI6IjIwMjYifSx7Im4iOiIyMDI1IiwidiI6IjIwMjUifSx7Im4iOiIyMDI0IiwidiI6IjIwMjQifSx7Im4iOiIyMDIzIiwidiI6IjIwMjMifSx7Im4iOiIyMDIyIiwidiI6IjIwMjIifSx7Im4iOiIyMDIxIiwidiI6IjIwMjEifSx7Im4iOiIyMDIwIiwidiI6IjIwMjAifSx7Im4iOiIyMDE5IiwidiI6IjIwMTkifSx7Im4iOiIyMDE4IiwidiI6IjIwMTgifSx7Im4iOiIyMDE3IiwidiI6IjIwMTcifSx7Im4iOiIyMDE2IiwidiI6IjIwMTYifSx7Im4iOiIyMDE1IiwidiI6IjIwMTUifSx7Im4iOiIyMDE0IiwidiI6IjIwMTQifSx7Im4iOiIyMDEzIiwidiI6IjIwMTMifSx7Im4iOiIyMDEyIiwidiI6IjIwMTIifSx7Im4iOiIyMDExIiwidiI6IjIwMTEifSx7Im4iOiIyMDEwIiwidiI6IjIwMTAifSx7Im4iOiIyMDA5IiwidiI6IjIwMDkifSx7Im4iOiIyMDA4IiwidiI6IjIwMDgifSx7Im4iOiIyMDA3IiwidiI6IjIwMDcifSx7Im4iOiIyMDA2IiwidiI6IjIwMDYifSx7Im4iOiIyMDA1IiwidiI6IjIwMDUifSx7Im4iOiIyMDA0IiwidiI6IjIwMDQifV19LHsia2V5IjoibGV0dGVyIiwibmFtZSI6IuWtl+avjSIsInZhbHVlIjpbeyJuIjoi5a2X5q+NIiwidiI6IiJ9LHsibiI6IkEiLCJ2IjoiQSJ9LHsibiI6IkIiLCJ2IjoiQiJ9LHsibiI6IkMiLCJ2IjoiQyJ9LHsibiI6IkQiLCJ2IjoiRCJ9LHsibiI6IkUiLCJ2IjoiRSJ9LHsibiI6IkYiLCJ2IjoiRiJ9LHsibiI6IkciLCJ2IjoiRyJ9LHsibiI6IkgiLCJ2IjoiSCJ9LHsibiI6IkkiLCJ2IjoiSSJ9LHsibiI6IkoiLCJ2IjoiSiJ9LHsibiI6IksiLCJ2IjoiSyJ9LHsibiI6IkwiLCJ2IjoiTCJ9LHsibiI6Ik0iLCJ2IjoiTSJ9LHsibiI6Ik4iLCJ2IjoiTiJ9LHsibiI6Ik8iLCJ2IjoiTyJ9LHsibiI6IlAiLCJ2IjoiUCJ9LHsibiI6IlEiLCJ2IjoiUSJ9LHsibiI6IlIiLCJ2IjoiUiJ9LHsibiI6IlMiLCJ2IjoiUyJ9LHsibiI6IlQiLCJ2IjoiVCJ9LHsibiI6IlUiLCJ2IjoiVSJ9LHsibiI6IlYiLCJ2IjoiViJ9LHsibiI6IlciLCJ2IjoiVyJ9LHsibiI6IlgiLCJ2IjoiWCJ9LHsibiI6IlkiLCJ2IjoiWSJ9LHsibiI6IloiLCJ2IjoiWiJ9LHsibiI6IjAtOSIsInYiOiIwLTkifV19LHsia2V5IjoiYnkiLCJuYW1lIjoi5o6S5bqPIiwidmFsdWUiOlt7Im4iOiLml7bpl7TmjpLluo8iLCJ2IjoidGltZSJ9LHsibiI6IuS6uuawlOaOkuW6jyIsInYiOiJoaXRzIn0seyJuIjoi6K+E5YiG5o6S5bqPIiwidiI6InNjb3JlIn1dfV0sCiAgICAgICAgICAgICI0IjpbeyJrZXkiOiJjbGFzcyIsIm5hbWUiOiLliafmg4UiLCJ2YWx1ZSI6W3sibiI6IuWFqOmDqCIsInYiOiIifSx7Im4iOiLmg4XmhJ8iLCJ2Ijoi5oOF5oSfIn0seyJuIjoi56eR5bm7IiwidiI6IuenkeW5uyJ9LHsibiI6IueDreihgCIsInYiOiLng63ooYAifSx7Im4iOiLmjqjnkIYiLCJ2Ijoi5o6o55CGIn0seyJuIjoi5pCe56yRIiwidiI6IuaQnueskSJ9LHsibiI6IuWGkumZqSIsInYiOiLlhpLpmakifSx7Im4iOiLokJ3ojokiLCJ2Ijoi6JCd6I6JIn0seyJuIjoi5qCh5ZutIiwidiI6IuagoeWbrSJ9LHsibiI6IuWKqOS9nCIsInYiOiLliqjkvZwifSx7Im4iOiLmnLrmiJgiLCJ2Ijoi5py65oiYIn0seyJuIjoi6L+Q5YqoIiwidiI6Iui/kOWKqCJ9LHsibiI6IuaImOS6iSIsInYiOiLmiJjkuokifSx7Im4iOiLlsJHlubQiLCJ2Ijoi5bCR5bm0In0seyJuIjoi5bCR5aWzIiwidiI6IuWwkeWlsyJ9LHsibiI6IuekvuS8miIsInYiOiLnpL7kvJoifSx7Im4iOiLljp/liJsiLCJ2Ijoi5Y6f5YibIn0seyJuIjoi5Lqy5a2QIiwidiI6IuS6suWtkCJ9LHsibiI6IuebiuaZuiIsInYiOiLnm4rmmboifSx7Im4iOiLlirHlv5ciLCJ2Ijoi5Yqx5b+XIn0seyJuIjoi5YW25LuWIiwidiI6IuWFtuS7liJ9XX0seyJrZXkiOiJhcmVhIiwibmFtZSI6IuWcsOWMuiIsInZhbHVlIjpbeyJuIjoi5YWo6YOoIiwidiI6IiJ9LHsibiI6IuWbveS6pyIsInYiOiLlm73kuqcifSx7Im4iOiLml6XmnKwiLCJ2Ijoi5pel5pysIn0seyJuIjoi5qyn576OIiwidiI6Iuasp+e+jiJ9LHsibiI6IuWFtuS7liIsInYiOiLlhbbku5YifV19LHsia2V5IjoibGFuZyIsIm5hbWUiOiLor63oqIAiLCJ2YWx1ZSI6W3sibiI6IuWFqOmDqCIsInYiOiIifSx7Im4iOiLlm73or60iLCJ2Ijoi5Zu96K+tIn0seyJuIjoi6Iux6K+tIiwidiI6IuiLseivrSJ9LHsibiI6IueypOivrSIsInYiOiLnsqTor60ifSx7Im4iOiLpl73ljZfor60iLCJ2Ijoi6Ze95Y2X6K+tIn0seyJuIjoi6Z+p6K+tIiwidiI6IumfqeivrSJ9LHsibiI6IuaXpeivrSIsInYiOiLml6Xor60ifSx7Im4iOiLlhbblroMiLCJ2Ijoi5YW25a6DIn1dfSx7ImtleSI6InllYXIiLCJuYW1lIjoi5bm05Lu9IiwidmFsdWUiOlt7Im4iOiLlhajpg6giLCJ2IjoiIn0seyJuIjoiMjAyNiIsInYiOiIyMDI2In0seyJuIjoiMjAyNSIsInYiOiIyMDI1In0seyJuIjoiMjAyNCIsInYiOiIyMDI0In0seyJuIjoiMjAyMyIsInYiOiIyMDIzIn0seyJuIjoiMjAyMiIsInYiOiIyMDIyIn0seyJuIjoiMjAyMSIsInYiOiIyMDIxIn0seyJuIjoiMjAyMCIsInYiOiIyMDIwIn0seyJuIjoiMjAxOSIsInYiOiIyMDE5In0seyJuIjoiMjAxOCIsInYiOiIyMDE4In0seyJuIjoiMjAxNyIsInYiOiIyMDE3In0seyJuIjoiMjAxNiIsInYiOiIyMDE2In0seyJuIjoiMjAxNSIsInYiOiIyMDE1In0seyJuIjoiMjAxNCIsInYiOiIyMDE0In0seyJuIjoiMjAxMyIsInYiOiIyMDEzIn0seyJuIjoiMjAxMiIsInYiOiIyMDEyIn0seyJuIjoiMjAxMSIsInYiOiIyMDExIn0seyJuIjoiMjAxMCIsInYiOiIyMDEwIn1dfSx7ImtleSI6ImxldHRlciIsIm5hbWUiOiLlrZfmr40iLCJ2YWx1ZSI6W3sibiI6IuWtl+avjSIsInYiOiIifSx7Im4iOiJBIiwidiI6IkEifSx7Im4iOiJCIiwidiI6IkIifSx7Im4iOiJDIiwidiI6IkMifSx7Im4iOiJEIiwidiI6IkQifSx7Im4iOiJFIiwidiI6IkUifSx7Im4iOiJGIiwidiI6IkYifSx7Im4iOiJHIiwidiI6IkcifSx7Im4iOiJIIiwidiI6IkgifSx7Im4iOiJJIiwidiI6IkkifSx7Im4iOiJKIiwidiI6IkoifSx7Im4iOiJLIiwidiI6IksifSx7Im4iOiJMIiwidiI6IkwifSx7Im4iOiJNIiwidiI6Ik0ifSx7Im4iOiJOIiwidiI6Ik4ifSx7Im4iOiJPIiwidiI6Ik8ifSx7Im4iOiJQIiwidiI6IlAifSx7Im4iOiJRIiwidiI6IlEifSx7Im4iOiJSIiwidiI6IlIifSx7Im4iOiJTIiwidiI6IlMifSx7Im4iOiJUIiwidiI6IlQifSx7Im4iOiJVIiwidiI6IlUifSx7Im4iOiJWIiwidiI6IlYifSx7Im4iOiJXIiwidiI6IlcifSx7Im4iOiJYIiwidiI6IlgifSx7Im4iOiJZIiwidiI6IlkifSx7Im4iOiJaIiwidiI6IloifSx7Im4iOiIwLTkiLCJ2IjoiMC05In1dfSx7ImtleSI6ImJ5IiwibmFtZSI6IuaOkuW6jyIsInZhbHVlIjpbeyJuIjoi5pe26Ze05o6S5bqPIiwidiI6InRpbWUifSx7Im4iOiLkurrmsJTmjpLluo8iLCJ2IjoiaGl0cyJ9LHsibiI6IuivhOWIhuaOkuW6jyIsInYiOiJzY29yZSJ9XX1dCiAgICAgICAgfTsKICAgICAgICBydWxlLmZpbHRlciA9IGZpbHRlcnM7CiAgICB9LAogICAgCiAgICDmjqjojZA6IGFzeW5jIGZ1bmN0aW9uKCkgeyByZXR1cm4gYXdhaXQgdGhpcy7kuIDnuqcoJycsIDEsIHt9LCB7fSk7IH0sCiAgICAKICAgIOS4gOe6pzogYXN5bmMgZnVuY3Rpb24odGlkLCBwZywgZmlsdGVyLCBleHRlbmQpIHsKICAgICAgICBsZXQgdXJsID0gdGhpcy5ob3N0ICsgJy9zaG93LycgKyB0aWQgKyAnLScgKyAoZXh0ZW5kLmFyZWF8fCcnKSArICctJyArIChleHRlbmQuYnl8fCd0aW1lJykgKyAnLScgKyAoZXh0ZW5kLmNsYXNzfHwnJykgKyAnLScgKyAoZXh0ZW5kLmxhbmd8fCcnKSArICctJyArIChleHRlbmQubGV0dGVyfHwnJykgKyAnLS0tJyArIHBnICsgJy0tLScgKyAoZXh0ZW5kLnllYXJ8fCcnKSArICcuaHRtbCc7CiAgICAgICAgbGV0IGh0bWwgPSBhd2FpdCB0aGlzLnJlcXVlc3QodXJsKTsKICAgICAgICBsZXQgdmlkZW9zID0gW107CiAgICAgICAgbGV0IGl0ZW1zID0gcGRmYShodG1sLCAnLnB1YmxpYy1saXN0LWJveCcpOwogICAgICAgIGl0ZW1zLmZvckVhY2goZnVuY3Rpb24oaXRlbSkgewogICAgICAgICAgICBsZXQgdGl0bGUgPSBwZGZoKGl0ZW0sICcudGltZS10aXRsZSYmdGl0bGUnKSB8fCBwZGZoKGl0ZW0sICcudGltZS10aXRsZSYmVGV4dCcpOwogICAgICAgICAgICBsZXQgaW1nID0gcGRmaChpdGVtLCAnLmdlbi1tb3ZpZS1pbWcmJmRhdGEtc3JjJyk7CiAgICAgICAgICAgIGxldCByZW1hcmtzID0gcGRmaChpdGVtLCAnLnB1YmxpYy1saXN0LXN1YnRpdGxlJiZUZXh0Jyk7CiAgICAgICAgICAgIGxldCB1cmwgPSBwZGZoKGl0ZW0sICcucHVibGljLWxpc3QtZXhwJiZocmVmJyk7CiAgICAgICAgICAgIGlmICh1cmwpIHsKICAgICAgICAgICAgICAgIHVybCA9IHVybC5zdGFydHNXaXRoKCdodHRwJykgPyB1cmwgOiBydWxlLmhvc3QgKyB1cmw7CiAgICAgICAgICAgICAgICB2aWRlb3MucHVzaCh7CiAgICAgICAgICAgICAgICAgICAgdm9kX2lkOiB1cmwsCiAgICAgICAgICAgICAgICAgICAgdm9kX25hbWU6IHRpdGxlLAogICAgICAgICAgICAgICAgICAgIHZvZF9waWM6IGltZy5zdGFydHNXaXRoKCdodHRwJykgPyBpbWcgOiBydWxlLmhvc3QgKyBpbWcsCiAgICAgICAgICAgICAgICAgICAgdm9kX3JlbWFya3M6IHJlbWFya3MKICAgICAgICAgICAgICAgIH0pOwogICAgICAgICAgICB9CiAgICAgICAgfSk7CiAgICAgICAgcmV0dXJuIHZpZGVvczsKICAgIH0sCgogICAg5LqM57qnOiBhc3luYyBmdW5jdGlvbihpZHMpIHsKICAgICAgICBsZXQgaHRtbCA9IGF3YWl0IHRoaXMucmVxdWVzdChpZHNbMF0pOwogICAgICAgIGxldCB2b2QgPSB7CiAgICAgICAgICAgIHZvZF9pZDogaWRzWzBdLAogICAgICAgICAgICB2b2RfbmFtZTogcGRmaChodG1sLCAnLnNsaWRlLWluZm8tdGl0bGUmJlRleHQnKSB8fCBwZGZoKGh0bWwsICdoMSYmVGV4dCcpLAogICAgICAgICAgICB2b2RfcGljOiBwZGZoKGh0bWwsICcuZGV0YWlsLXBpYyBpbWcmJmRhdGEtc3JjJykgfHwgcGRmaChodG1sLCAnLmRldGFpbC1waWMgaW1nJiZzcmMnKSwKICAgICAgICAgICAgdm9kX2NvbnRlbnQ6IHBkZmgoaHRtbCwgJyNoZWlnaHRfbGltaXQmJlRleHQnKS50cmltKCksCiAgICAgICAgICAgIHZvZF9wbGF5X2Zyb206ICcnLCB2b2RfcGxheV91cmw6ICcnCiAgICAgICAgfTsKICAgICAgICBsZXQgaW5mb3MgPSBwZGZhKGh0bWwsICcuc2xpZGUtaW5mbycpOwogICAgICAgIGluZm9zLmZvckVhY2goZnVuY3Rpb24oaW5mbykgewogICAgICAgICAgICBsZXQgdGV4dCA9IHBkZmgoaW5mbywgJ2JvZHkmJlRleHQnKTsKICAgICAgICAgICAgaWYgKHRleHQuaW5jbHVkZXMoJ+Wkh+azqCcpKSB2b2Qudm9kX3JlbWFya3MgPSB0ZXh0LnNwbGl0KCc6JylbMV0udHJpbSgpOwogICAgICAgICAgICBlbHNlIGlmICh0ZXh0LmluY2x1ZGVzKCflr7zmvJQnKSkgdm9kLnZvZF9kaXJlY3RvciA9IHRleHQuc3BsaXQoJzonKVsxXS50cmltKCk7CiAgICAgICAgICAgIGVsc2UgaWYgKHRleHQuaW5jbHVkZXMoJ+a8lOWRmCcpKSB2b2Qudm9kX2FjdG9yID0gdGV4dC5zcGxpdCgnOicpWzFdLnRyaW0oKTsKICAgICAgICAgICAgZWxzZSBpZiAodGV4dC5pbmNsdWRlcygn5pu05pawJykpIHsgbGV0IHkgPSB0ZXh0Lm1hdGNoKC9cZHs0fS8pOyBpZiAoeSkgdm9kLnZvZF95ZWFyID0geVswXTsgfQogICAgICAgIH0pOwogICAgICAgIGlmICghdm9kLnZvZF95ZWFyKSB7IGxldCB5ID0gcGRmaChodG1sLCAnLnNsaWRlLWluZm8tcmVtYXJrcyBhW2hyZWYqPSIyMDIiXSYmVGV4dCcpOyBpZih5KSB2b2Qudm9kX3llYXIgPSB5OyB9CiAgICAgICAgdm9kLnZvZF9hcmVhID0gcGRmaChodG1sLCAnLnNsaWRlLWluZm8tcmVtYXJrcyBhW2hyZWYqPSLlhoXlnLAiXSYmVGV4dCcpIHx8IHBkZmgoaHRtbCwgJy5zbGlkZS1pbmZvLXJlbWFya3MgYVtocmVmKj0i576O5Zu9Il0mJlRleHQnKTsKICAgICAgICBsZXQgcGxheUZyb20gPSBbXTsKICAgICAgICBsZXQgcGxheVVybCA9IFtdOwogICAgICAgIGxldCB0YWJzID0gcGRmYShodG1sLCAnLmFudGhvbG9neS10YWIgLnN3aXBlci1zbGlkZScpOwogICAgICAgIGxldCBsaXN0cyA9IHBkZmEoaHRtbCwgJy5hbnRob2xvZ3ktbGlzdC1ib3gnKTsKICAgICAgICBpZiAodGFicy5sZW5ndGggPT09IDAgJiYgbGlzdHMubGVuZ3RoID4gMCkgdGFicyA9IFt7bmFtZTon5pKt5pS+57q/6LevJ31dOwogICAgICAgIGZvciAobGV0IGkgPSAwOyBpIDwgbGlzdHMubGVuZ3RoOyBpKyspIHsKICAgICAgICAgICAgbGV0IHRhYk5hbWUgPSAn57q/6LevJyArIChpKzEpOwogICAgICAgICAgICBpZiAoaSA8IHRhYnMubGVuZ3RoICYmIHRhYnNbaV0ubmFtZSAhPT0gJ+aSreaUvue6v+i3rycpIHsKICAgICAgICAgICAgICAgIGxldCB0YWJIdG1sID0gdGFic1tpXTsKICAgICAgICAgICAgICAgIGxldCBmcm9tQXR0ciA9IHBkZmgodGFiSHRtbCwgJ2EmJmRhdGEtZm9ybScpIHx8IHBkZmgodGFiSHRtbCwgJ2EmJmRhdGEtZnJvbScpOwogICAgICAgICAgICAgICAgbGV0IHRleHQgPSBwZGZoKHRhYkh0bWwsICdib2R5JiZUZXh0Jyk7CiAgICAgICAgICAgICAgICB0ZXh0ID0gdGV4dC5yZXBsYWNlKC9cZCskLywgJycpLnRyaW0oKTsKICAgICAgICAgICAgICAgIHRhYk5hbWUgPSB0ZXh0IHx8IGZyb21BdHRyIHx8IHRhYk5hbWU7CiAgICAgICAgICAgIH0KICAgICAgICAgICAgcGxheUZyb20ucHVzaCh0YWJOYW1lKTsKICAgICAgICAgICAgbGV0IHVybHMgPSBbXTsKICAgICAgICAgICAgbGV0IGl0ZW1zID0gcGRmYShsaXN0c1tpXSwgJy5hbnRob2xvZ3ktbGlzdC1wbGF5IGEnKTsKICAgICAgICAgICAgZm9yIChsZXQgaiA9IDA7IGogPCBpdGVtcy5sZW5ndGg7IGorKykgewogICAgICAgICAgICAgICAgbGV0IGl0ZW1IdG1sID0gaXRlbXNbal07CiAgICAgICAgICAgICAgICBsZXQgbmFtZSA9IHBkZmgoaXRlbUh0bWwsICdhJiZUZXh0Jyk7CiAgICAgICAgICAgICAgICBsZXQgaHJlZiA9IHBkZmgoaXRlbUh0bWwsICdhJiZocmVmJyk7CiAgICAgICAgICAgICAgICBpZiAoIWhyZWYgfHwgaHJlZiA9PT0gJyMnIHx8IGhyZWYudG9Mb3dlckNhc2UoKS5zdGFydHNXaXRoKCdqYXZhc2NyaXB0JykgfHwgaHJlZi5sZW5ndGggPCAzKSBjb250aW51ZTsKICAgICAgICAgICAgICAgIGlmICghbmFtZSkgewogICAgICAgICAgICAgICAgICAgIGxldCBtYXRjaCA9IGhyZWYubWF0Y2goLy0oXGQrKVwuaHRtbCQvKTsKICAgICAgICAgICAgICAgICAgICBuYW1lID0gbWF0Y2ggPyAn56ysJyArIG1hdGNoWzFdICsgJ+mbhicgOiAn56ysJyArIChqICsgMSkgKyAn6ZuGJzsKICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgIGxldCBmdWxsVXJsID0gaHJlZi5zdGFydHNXaXRoKCdodHRwJykgPyBocmVmIDogcnVsZS5ob3N0ICsgaHJlZjsKICAgICAgICAgICAgICAgIHVybHMucHVzaChuYW1lICsgJyQnICsgZnVsbFVybCArICdAJyArIHRhYk5hbWUpOwogICAgICAgICAgICB9CiAgICAgICAgICAgIHBsYXlVcmwucHVzaCh1cmxzLmpvaW4oJyMnKSk7CiAgICAgICAgfQogICAgICAgIHZvZC52b2RfcGxheV9mcm9tID0gcGxheUZyb20uam9pbignJCQkJyk7CiAgICAgICAgdm9kLnZvZF9wbGF5X3VybCA9IHBsYXlVcmwuam9pbignJCQkJyk7CiAgICAgICAgcmV0dXJuIHZvZDsKICAgIH0sCgogICAg5pCc57SiOiBhc3luYyBmdW5jdGlvbihrZXksIHF1aWNrLCBwZykgewogICAgICAgIGxldCB1cmwgPSB0aGlzLmhvc3QgKyAnL25rLy0tLS0tLS0tLS0tLS0uaHRtbD93ZD0nICsgZW5jb2RlVVJJQ29tcG9uZW50KGtleSk7CiAgICAgICAgbGV0IGh0bWwgPSBhd2FpdCB0aGlzLnJlcXVlc3QodXJsKTsKICAgICAgICBsZXQgdmlkZW9zID0gW107CiAgICAgICAgbGV0IGl0ZW1zID0gcGRmYShodG1sLCAnLnB1YmxpYy1saXN0LWJveC5zZWFyY2gtYm94Jyk7CiAgICAgICAgaXRlbXMuZm9yRWFjaChmdW5jdGlvbihpdGVtKSB7CiAgICAgICAgICAgIGxldCB0aXRsZSA9IHBkZmgoaXRlbSwgJy50aHVtYi10eHQgYSYmVGV4dCcpIHx8IHBkZmgoaXRlbSwgJy50aW1lLXRpdGxlJiZUZXh0Jyk7CiAgICAgICAgICAgIGxldCBpbWcgPSBwZGZoKGl0ZW0sICcuZ2VuLW1vdmllLWltZyYmZGF0YS1zcmMnKTsKICAgICAgICAgICAgbGV0IHJlbWFya3MgPSBwZGZoKGl0ZW0sICcucHVibGljLWxpc3QtcHJiJiZUZXh0JykgfHwgcGRmaChpdGVtLCAnLnB1YmxpYy1saXN0LXN1YnRpdGxlJiZUZXh0Jyk7CiAgICAgICAgICAgIGxldCB1cmwgPSBwZGZoKGl0ZW0sICcucHVibGljLWxpc3QtZXhwJiZocmVmJyk7CiAgICAgICAgICAgIGlmICh1cmwpIHsKICAgICAgICAgICAgICAgIHVybCA9IHVybC5zdGFydHNXaXRoKCdodHRwJykgPyB1cmwgOiBydWxlLmhvc3QgKyB1cmw7CiAgICAgICAgICAgICAgICB2aWRlb3MucHVzaCh7CiAgICAgICAgICAgICAgICAgICAgdm9kX2lkOiB1cmwsCiAgICAgICAgICAgICAgICAgICAgdm9kX25hbWU6IHRpdGxlLAogICAgICAgICAgICAgICAgICAgIHZvZF9waWM6IGltZy5zdGFydHNXaXRoKCdodHRwJykgPyBpbWcgOiBydWxlLmhvc3QgKyBpbWcsCiAgICAgICAgICAgICAgICAgICAgdm9kX3JlbWFya3M6IHJlbWFya3MKICAgICAgICAgICAgICAgIH0pOwogICAgICAgICAgICB9CiAgICAgICAgfSk7CiAgICAgICAgcmV0dXJuIHZpZGVvczsKICAgIH0sCgogICAgbGF6eTogYXN5bmMgZnVuY3Rpb24oZmxhZywgaWQsIGZsYWdzKSB7CiAgICAgICAgdHJ5IHsKICAgICAgICAgICAgbGV0IHBhcnRzID0gaWQuc3BsaXQoJ0AnKTsKICAgICAgICAgICAgbGV0IHBsYXlVcmwgPSBwYXJ0c1swXTsKICAgICAgICAgICAgaWYgKHBsYXlVcmwgPT09IHRoaXMuaG9zdCB8fCBwbGF5VXJsID09PSB0aGlzLmhvc3QgKyAnLycpIHJldHVybiB7IHBhcnNlOiAxLCB1cmw6IHBsYXlVcmwgfTsKICAgICAgICAgICAgbGV0IGh0bWwgPSBhd2FpdCB0aGlzLnJlcXVlc3QocGxheVVybCwge2hlYWRlcnM6IHtSZWZlcmVyOiBwbGF5VXJsfX0pOwogICAgICAgICAgICBsZXQgc3RhcnQgPSAndmFyIHBsYXllcl9hYWFhPSc7CiAgICAgICAgICAgIGxldCBpZHggPSBodG1sLmluZGV4T2Yoc3RhcnQpOwogICAgICAgICAgICBpZiAoaWR4ID09PSAtMSkgcmV0dXJuIHsgcGFyc2U6IDEsIHVybDogcGxheVVybCB9OwogICAgICAgICAgICBsZXQganNvblN0ciA9IGh0bWwuc3Vic3RyaW5nKGlkeCArIHN0YXJ0Lmxlbmd0aCk7CiAgICAgICAgICAgIGxldCBlbmRJZHggPSBqc29uU3RyLmluZGV4T2YoJzwvc2NyaXB0PicpOwogICAgICAgICAgICBpZiAoZW5kSWR4ICE9PSAtMSkganNvblN0ciA9IGpzb25TdHIuc3Vic3RyaW5nKDAsIGVuZElkeCk7CiAgICAgICAgICAgIGxldCBqc29uID0gSlNPTi5wYXJzZShqc29uU3RyLnRyaW0oKS5yZXBsYWNlKC87JC8sICcnKSk7CiAgICAgICAgICAgIGlmIChqc29uLnVybCAmJiAvbTN1OHxtcDQvaS50ZXN0KGpzb24udXJsKSkgcmV0dXJuIHsgcGFyc2U6IDAsIHVybDoganNvbi51cmwgfTsKICAgICAgICAgICAgbGV0IGNmZyA9IChhd2FpdCByZXEodGhpcy5ob3N0ICsgJy9zdGF0aWMvanMvcGxheWVyY29uZmlnLmpzP3Q9MjAyNTEyMjUnLCB7aGVhZGVyczp0aGlzLmhlYWRlcnN9KSkuY29udGVudDsKICAgICAgICAgICAgbGV0IHBhcnNlRG9tYWluID0gJ2h0dHBzOi8vZ2cueG4tLWl0LWlmN2MxOWc1czRicHM1Yy5jb20vbmt2b2QzLnBocCc7CiAgICAgICAgICAgIGxldCBqeCA9IHBhcnNlRG9tYWluICsgKHBhcnNlRG9tYWluLmluY2x1ZGVzKCc/JykgPyAnJicgOiAnPycpICsgJ3VybD0nICsgZW5jb2RlVVJJQ29tcG9uZW50KGpzb24udXJsKSArICcmbmV4dD0nICsgZW5jb2RlVVJJQ29tcG9uZW50KGpzb24ubGlua19uZXh0fHwnJykgKyAnJnRpdGxlPScgKyBlbmNvZGVVUklDb21wb25lbnQoanNvbi52b2RfZGF0YT8udm9kX25hbWV8fCcnKTsKICAgICAgICAgICAgbGV0IGp4SHRtbCA9IChhd2FpdCByZXEoangsIHtoZWFkZXJzOiB7UmVmZXJlcjogcGxheVVybCwgJ1VzZXItQWdlbnQnOiB0aGlzLmhlYWRlcnNbJ1VzZXItQWdlbnQnXX19KSkuY29udGVudDsKICAgICAgICAgICAgbGV0IGtleU1hdGNoID0ganhIdG1sLm1hdGNoKC92YXJccytyYXdfa2V5XHMqPVxzKlxbKC4qPylcXS8pOwogICAgICAgICAgICBsZXQgaXZNYXRjaCA9IGp4SHRtbC5tYXRjaCgvdmFyXHMraXZccyo9XHMqXFsoLio/KVxdLyk7CiAgICAgICAgICAgIGxldCBlbmNNYXRjaCA9IGp4SHRtbC5tYXRjaCgvdmFyXHMrZW5jcnlwdGVkXHMqPVxzKlsiJ10oLio/KVsiJ10vKTsKICAgICAgICAgICAgaWYgKCFrZXlNYXRjaCB8fCAhaXZNYXRjaCB8fCAhZW5jTWF0Y2gpIHJldHVybiB7IHBhcnNlOiAxLCB1cmw6IHBsYXlVcmwgfTsKICAgICAgICAgICAgbGV0IGtleSA9IGtleU1hdGNoWzFdLnNwbGl0KCcsJykubWFwKE51bWJlcik7CiAgICAgICAgICAgIGxldCBpdiA9IGl2TWF0Y2hbMV0uc3BsaXQoJywnKS5tYXAoTnVtYmVyKTsKICAgICAgICAgICAgbGV0IGVuYyA9IGVuY01hdGNoWzFdOwogICAgICAgICAgICBsZXQgZGVjID0gdGhpcy5DcnlwdG9Ub29sLmRlY3J5cHREYXRhKGVuYywga2V5LCBpdik7CiAgICAgICAgICAgIGlmKCFkZWMpIHJldHVybiB7IHBhcnNlOiAxLCB1cmw6IHBsYXlVcmwgfTsKICAgICAgICAgICAgbGV0IGZpbmFsID0gJyc7CiAgICAgICAgICAgIGxldCByYW5kb21NYXRjaCA9IGRlYy5tYXRjaCgvZ2V0cmFuZG9tXChbJyJdKC4qPylbJyJdXCkvKTsKICAgICAgICAgICAgaWYgKHJhbmRvbU1hdGNoKSB7CiAgICAgICAgICAgICAgICBmaW5hbCA9IHRoaXMuQ3J5cHRvVG9vbC5nZXRyYW5kb20ocmFuZG9tTWF0Y2hbMV0pOwogICAgICAgICAgICB9IGVsc2UgewogICAgICAgICAgICAgICAgbGV0IHVybE1hdGNoID0gZGVjLm1hdGNoKC9odHRwcz86XC9cL1teXHMiJzw+XSsvKTsKICAgICAgICAgICAgICAgIGlmKHVybE1hdGNoKSBmaW5hbCA9IHVybE1hdGNoWzBdOwogICAgICAgICAgICB9CiAgICAgICAgICAgIHJldHVybiB7IHBhcnNlOiAwLCB1cmw6IGZpbmFsLCBoZWFkZXI6IHsgJ1VzZXItQWdlbnQnOiB0aGlzLmhlYWRlcnNbJ1VzZXItQWdlbnQnXSB9IH07CiAgICAgICAgfSBjYXRjaCAoZSkgeyByZXR1cm4geyBwYXJzZTogMSwgdXJsOiBpZCB9OyB9CiAgICB9Cn07Cg== \ No newline at end of file diff --git "a/spider/js/\350\256\276\347\275\256\344\270\255\345\277\203.js" "b/spider/js/\350\256\276\347\275\256\344\270\255\345\277\203.js" index 0a607edd..49c5cf27 100644 --- "a/spider/js/\350\256\276\347\275\256\344\270\255\345\277\203.js" +++ "b/spider/js/\350\256\276\347\275\256\344\270\255\345\277\203.js" @@ -401,6 +401,8 @@ var rule = { d.push(getInput('get_enable_dr2', '查看drpy2源启用状态', images.settings)); d.push(genMultiInput('enable_py', '设置py源启用状态', '设置为1可启用此功能,设置为2启用T4(默认没设置也属于启动,设置其他值关闭)', images.settings)); d.push(getInput('get_enable_py', '查看py源启用状态', images.settings)); + d.push(genMultiInput('enable_php', '设置php源启用状态', '设置为1可启用此功能,设置为0可关闭(1-T4|2-T3 默认没设置也属于启动1,设置其他值关闭)', images.settings)); + d.push(getInput('get_enable_php', '查看php源启用状态', images.settings)); d.push(genMultiInput('enable_cat', '设置cat源启用状态', '设置为1可启用此功能,设置为2启用T4(默认没设置也属于启动,设置其他值关闭)', images.settings)); d.push(getInput('get_enable_cat', '查看cat源启用状态', images.settings)); d.push(genMultiInput('enable_old_config', '设置兼容性配置', '设置为1可启用此功能(默认关闭)', images.settings)); @@ -457,6 +459,9 @@ var rule = { d.push(genMultiInput('PROXY_AUTH', '设置代理播放授权', '默认为drpys,可自行配置成其他值', images.settings)); d.push(getInput('get_PROXY_AUTH', '查看代理播放授权', images.settings)); + + d.push(genMultiInput('enable_self_jx', '设置启用自建解析', '默认为关闭,可自行配置成其他值(0关闭 1启用)', images.settings)); + d.push(getInput('get_enable_self_jx', '查看启用自建解析', images.settings)); break; } return d @@ -1272,6 +1277,7 @@ var rule = { 'play_proxy_mode', 'enable_dr2', 'enable_py', + 'enable_php', 'enable_cat', 'enable_old_config', 'enable_rule_name', @@ -1290,6 +1296,7 @@ var rule = { 'must_sub_code', 'mg_hz', 'PROXY_AUTH', + 'enable_self_jx', ]; let get_cookie_sets = [ 'get_quark_cookie', @@ -1306,6 +1313,7 @@ var rule = { 'get_play_proxy_mode', 'get_enable_dr2', 'get_enable_py', + 'get_enable_php', 'get_enable_cat', 'get_enable_old_config', 'get_enable_rule_name', @@ -1324,6 +1332,7 @@ var rule = { 'get_must_sub_code', 'get_mg_hz', 'get_PROXY_AUTH', + 'get_enable_self_jx', ]; if (cookie_sets.includes(action) && value) { try { diff --git "a/spider/js/\350\277\275\346\226\260\345\275\261\350\247\206[\344\274\230].js" "b/spider/js/\350\277\275\346\226\260\345\275\261\350\247\206[\344\274\230].js" new file mode 100644 index 00000000..03dd5afa --- /dev/null +++ "b/spider/js/\350\277\275\346\226\260\345\275\261\350\247\206[\344\274\230].js" @@ -0,0 +1,13 @@ +/* +@header({ + searchable: 1, + filterable: 1, + quickSearch: 0, + title: '追新影视', + author: 'EylinSir', + '类型': '影视', + lang: 'ds' +}) +*/ + +dmFyIHJ1bGUgPSB7CiAgICDnsbvlnos6ICflvbHop4YnLAogICAgYXV0aG9yOiAnRXlsaW5TaXInLAogICAgdGl0bGU6ICfov73mlrDlvbHop4YnLAogICAgaG9zdDogJ2h0dHA6Ly96eHlzLmdhb3pob3Vrai5jbicsCiAgICBob21lVXJsOiAnaHR0cDovL3p4eXMuZ2FvemhvdWtqLmNuJywKICAgIHVybDogJy9wdWJsaWMvP3NlcnZpY2U9QXBwLk1vdi5HZXRPbmxpbmVMaXN0JnR5cGVfaWQ9ZnljbGFzcyZwYWdlPWZ5cGFnZSZsaW1pdD0xOCcsCiAgICBzZWFyY2hVcmw6ICcvcHVibGljLz9zZXJ2aWNlPUFwcC5Nb3YuU2VhcmNoVm9kJmtleT0qKicsCiAgICBzZWFyY2hhYmxlOiAxLAogICAgcXVpY2tTZWFyY2g6IDAsCiAgICBmaWx0ZXJhYmxlOiAxLAogICAgdGltZW91dDogMTAwMDAsCiAgICBwbGF5X3BhcnNlOiB0cnVlLAogICAga2V5OiAnJywKICAgIGl2OiAnMTIzNDU2Nzg5MDEyMzQ1NicsCiAgICBoZWFkZXJzOiB7CiAgICAgICAgJ1VzZXItQWdlbnQnOiAnb2todHRwLzMuMTIuMCcsCiAgICAgICAgJ0Nvbm5lY3Rpb24nOiAnS2VlcC1BbGl2ZScsCiAgICAgICAgJ0FjY2VwdC1FbmNvZGluZyc6ICdnemlwJwogICAgfSwKICAgIAogICAgY2xhc3NfcGFyc2U6IGFzeW5jIGZ1bmN0aW9uICgpIHsKICAgICAgICBpZiAoIXRoaXMuaG9zdCkgcmV0dXJuIHsgY2xhc3M6IFtdLCBmaWx0ZXJzOiB7fSB9OwogICAgICAgIHJldHVybiB7CiAgICAgICAgICAgIGNsYXNzOiBbCiAgICAgICAgICAgICAgICB7ICd0eXBlX2lkJzogJzEnLCAndHlwZV9uYW1lJzogJ+eUteW9sScgfSwKICAgICAgICAgICAgICAgIHsgJ3R5cGVfaWQnOiAnMicsICd0eXBlX25hbWUnOiAn6L+e57ut5YmnJyB9LAogICAgICAgICAgICAgICAgeyAndHlwZV9pZCc6ICczJywgJ3R5cGVfbmFtZSc6ICfnu7zoibonIH0sCiAgICAgICAgICAgICAgICB7ICd0eXBlX2lkJzogJzQnLCAndHlwZV9uYW1lJzogJ+WKqOa8qycgfQogICAgICAgICAgICBdLAogICAgICAgICAgICBmaWx0ZXJzOiB7fQogICAgICAgIH07CiAgICB9LAoKICAgIOmihOWkhOeQhjogYXN5bmMgZnVuY3Rpb24gKCkgewogICAgICAgIHRyeSB7CiAgICAgICAgICAgIGxldCByZXNwMSA9IGF3YWl0IF9mZXRjaChgJHt0aGlzLmhvc3R9L3B1YmxpYy8/c2VydmljZT1BcHAuTW92LkdldFR5cGVMaXN0YCwgeyBoZWFkZXJzOiB0aGlzLmhlYWRlcnMgfSk7CiAgICAgICAgICAgIGxldCB0ZXh0MSA9IGF3YWl0IHJlc3AxLnRleHQoKTsKICAgICAgICAgICAgaWYgKHRleHQxLnN0YXJ0c1dpdGgoJ++7vycpKSB0ZXh0MSA9IHRleHQxLnN1YnN0cmluZygxKTsKICAgICAgICAgICAgbGV0IGRhdGExID0gSlNPTi5wYXJzZSh0ZXh0MSk7CiAgICAgICAgICAgIGxldCBzaWduX3N0YXJ0ID0gKGRhdGExLkRhdGEgfHwgW10pLmZpbmQoaSA9PiBpLnR5cGVfaWQudG9TdHJpbmcoKSA9PT0gJzEnKT8udHlwZV91bmlvbiB8fCAnJzsKICAgICAgICAgICAgbGV0IHJlc3AyID0gYXdhaXQgX2ZldGNoKGAke3RoaXMuaG9zdH0vcHVibGljLz9zZXJ2aWNlPUFwcC5Nb3YuR2V0QWRUeXBlYCwgeyBoZWFkZXJzOiB0aGlzLmhlYWRlcnMgfSk7CiAgICAgICAgICAgIGxldCB0ZXh0MiA9IGF3YWl0IHJlc3AyLnRleHQoKTsKICAgICAgICAgICAgaWYgKHRleHQyLnN0YXJ0c1dpdGgoJ++7vycpKSB0ZXh0MiA9IHRleHQyLnN1YnN0cmluZygxKTsKICAgICAgICAgICAgbGV0IGRhdGEyID0gSlNPTi5wYXJzZSh0ZXh0Mik7CiAgICAgICAgICAgIGxldCBzaWduX2VuZCA9IGRhdGEyLkRhdGEudG1wIHx8ICcnOwogICAgICAgICAgICBsZXQgZnVsbEtleSA9IHNpZ25fc3RhcnQgKyBzaWduX2VuZDsKICAgICAgICAgICAgaWYgKGZ1bGxLZXkubGVuZ3RoID49IDE2KSB7CiAgICAgICAgICAgICAgICB0aGlzLmtleSA9IGZ1bGxLZXkuc3Vic3RyaW5nKDAsIDE2KTsKICAgICAgICAgICAgfQogICAgICAgIH0gY2F0Y2ggKGUpIHsKICAgICAgICAgICAgY29uc29sZS5lcnJvcign5Yid5aeL5YyWIEtleSDlpLHotKU6JywgZSk7CiAgICAgICAgICAgIHRoaXMuaG9zdCA9ICcnOwogICAgICAgIH0KICAgIH0sCiAgICAKICAgIOaOqOiNkDogYXN5bmMgZnVuY3Rpb24gKCkgewogICAgICAgIGlmICghdGhpcy5ob3N0KSByZXR1cm4gc2V0UmVzdWx0KFtdKTsKICAgICAgICBsZXQgcmVzcCA9IGF3YWl0IF9mZXRjaChgJHt0aGlzLmhvc3R9L3B1YmxpYy8/c2VydmljZT1BcHAuTW92LkdldEhvbWVMZXZlbGAsIHsgaGVhZGVyczogdGhpcy5oZWFkZXJzIH0pOwogICAgICAgIGxldCB0ZXh0ID0gYXdhaXQgcmVzcC50ZXh0KCk7CiAgICAgICAgaWYgKHRleHQuc3RhcnRzV2l0aCgn77u/JykpIHRleHQgPSB0ZXh0LnN1YnN0cmluZygxKTsKICAgICAgICBsZXQgZGF0YSA9IEpTT04ucGFyc2UodGV4dCk7CiAgICAgICAgCiAgICAgICAgbGV0IHZpZGVvcyA9IHR5cGVvZiBkYXRhID09PSAnb2JqZWN0JyAmJiBkYXRhICE9PSBudWxsID8gCiAgICAgICAgICAgIE9iamVjdC52YWx1ZXMoZGF0YSkKICAgICAgICAgICAgICAgIC5maWx0ZXIoaXRlbSA9PiB0eXBlb2YgaXRlbSA9PT0gJ29iamVjdCcgJiYgaXRlbSAhPT0gbnVsbCkKICAgICAgICAgICAgICAgIC5mbGF0TWFwKGl0ZW0gPT4gT2JqZWN0LnZhbHVlcyhpdGVtKSkKICAgICAgICAgICAgICAgIC5maWx0ZXIoQXJyYXkuaXNBcnJheSkKICAgICAgICAgICAgICAgIC5mbGF0TWFwKGxpc3QgPT4gbGlzdC5tYXAoayA9PiAoewogICAgICAgICAgICAgICAgICAgIHRpdGxlOiBrLnZvZF9uYW1lLAogICAgICAgICAgICAgICAgICAgIHVybDogYCR7dGhpcy5ob3N0fS9wdWJsaWMvP3NlcnZpY2U9QXBwLk1vdi5HZXRPbmxpbmVNdkJ5SWQmdm9kaWQ9JHtrLnZvZF9pZH1gLAogICAgICAgICAgICAgICAgICAgIGRlc2M6IGsudm9kX3JlbWFya3MsCiAgICAgICAgICAgICAgICAgICAgcGljX3VybDogay52b2RfcGljLAogICAgICAgICAgICAgICAgICAgIHZvZF95ZWFyOiBrLnZvZF95ZWFyLAogICAgICAgICAgICAgICAgICAgIGNvbnRlbnQ6IGsudm9kX2NvbnRlbnQKICAgICAgICAgICAgICAgIH0pKSkKICAgICAgICAgICAgOiBbXTsKICAgICAgICAKICAgICAgICByZXR1cm4gc2V0UmVzdWx0KHZpZGVvcyk7CiAgICB9LAogICAgCiAgICDkuIDnuqc6IGFzeW5jIGZ1bmN0aW9uICh0aWQsIHBnLCBmaWx0ZXIsIGV4dGVuZCkgewogICAgICAgIGlmICghdGhpcy5ob3N0KSByZXR1cm4gc2V0UmVzdWx0KFtdKTsKICAgICAgICBsZXQgdXJsID0gYCR7dGhpcy5ob3N0fS9wdWJsaWMvP3NlcnZpY2U9QXBwLk1vdi5HZXRPbmxpbmVMaXN0JnR5cGVfaWQ9JHt0aWR9JnBhZ2U9JHtwZ30mbGltaXQ9MThgOwogICAgICAgIGxldCByZXNwID0gYXdhaXQgX2ZldGNoKHVybCwgeyBoZWFkZXJzOiB0aGlzLmhlYWRlcnMgfSk7CiAgICAgICAgbGV0IHRleHQgPSBhd2FpdCByZXNwLnRleHQoKTsKICAgICAgICBpZiAodGV4dC5zdGFydHNXaXRoKCfvu78nKSkgdGV4dCA9IHRleHQuc3Vic3RyaW5nKDEpOwogICAgICAgIGxldCBkYXRhID0gSlNPTi5wYXJzZSh0ZXh0KTsKICAgICAgICBsZXQgdmlkZW9zID0gKGRhdGEuRGF0YSB8fCBbXSkubWFwKGkgPT4gKHsKICAgICAgICAgICAgdGl0bGU6IGkudm9kX25hbWUsCiAgICAgICAgICAgIHVybDogYCR7dGhpcy5ob3N0fS9wdWJsaWMvP3NlcnZpY2U9QXBwLk1vdi5HZXRPbmxpbmVNdkJ5SWQmdm9kaWQ9JHtpLnZvZF9pZH1gLAogICAgICAgICAgICBkZXNjOiBpLnZvZF9yZW1hcmtzLAogICAgICAgICAgICBwaWNfdXJsOiBpLnZvZF9waWMsCiAgICAgICAgICAgIHZvZF95ZWFyOiBpLnZvZF95ZWFyLAogICAgICAgICAgICBjb250ZW50OiBpLnZvZF9jb250ZW50CiAgICAgICAgfSkpOwogICAgICAgIHJldHVybiBzZXRSZXN1bHQodmlkZW9zKTsKICAgIH0sCiAgICAKICAgIOS6jOe6pzogYXN5bmMgZnVuY3Rpb24gKCkgewogICAgICAgIGxldCB2b2RJZCA9IHRoaXMuaW5wdXQubWF0Y2goL3ZvZGlkPShcZCspLylbMV07CiAgICAgICAgbGV0IHVybCA9IGAke3RoaXMuaG9zdH0vcHVibGljLz9zZXJ2aWNlPUFwcC5Nb3YuR2V0T25saW5lTXZCeUlkJnZvZGlkPSR7dm9kSWR9YDsKICAgICAgICBsZXQgcmVzcCA9IGF3YWl0IF9mZXRjaCh1cmwsIHsgaGVhZGVyczogdGhpcy5oZWFkZXJzIH0pOwogICAgICAgIGxldCB0ZXh0ID0gYXdhaXQgcmVzcC50ZXh0KCk7CiAgICAgICAgaWYgKHRleHQuc3RhcnRzV2l0aCgn77u/JykpIHRleHQgPSB0ZXh0LnN1YnN0cmluZygxKTsKICAgICAgICBsZXQgZGF0YSA9IEpTT04ucGFyc2UodGV4dCk7CiAgICAgICAgbGV0IGZpcnN0SXRlbSA9IChkYXRhLkRhdGEgfHwgW10pLmZpbmQoaSA9PiB0eXBlb2YgaSA9PT0gJ29iamVjdCcgJiYgaSAhPT0gbnVsbCk7CiAgICAgICAgCiAgICAgICAgaWYgKGZpcnN0SXRlbSkgewogICAgICAgICAgICByZXR1cm4gewogICAgICAgICAgICAgICAgdm9kX2lkOiBmaXJzdEl0ZW0udm9kX2lkLnRvU3RyaW5nKCksCiAgICAgICAgICAgICAgICB2b2RfbmFtZTogZmlyc3RJdGVtLnZvZF9uYW1lLAogICAgICAgICAgICAgICAgdm9kX3BpYzogZmlyc3RJdGVtLnZvZF9waWMsCiAgICAgICAgICAgICAgICB2b2RfcmVtYXJrczogZmlyc3RJdGVtLnZvZF9yZW1hcmtzLAogICAgICAgICAgICAgICAgdm9kX3llYXI6IGZpcnN0SXRlbS52b2RfeWVhciwKICAgICAgICAgICAgICAgIHZvZF9hcmVhOiBmaXJzdEl0ZW0udm9kX2FyZWEsCiAgICAgICAgICAgICAgICB2b2RfYWN0b3I6IGZpcnN0SXRlbS52b2RfYWN0b3IsCiAgICAgICAgICAgICAgICB2b2RfY29udGVudDogZmlyc3RJdGVtLnZvZF9jb250ZW50LAogICAgICAgICAgICAgICAgdm9kX3BsYXlfZnJvbTogZmlyc3RJdGVtLnZvZF9wbGF5X2Zyb20sCiAgICAgICAgICAgICAgICB2b2RfcGxheV91cmw6IGZpcnN0SXRlbS52b2RfcGxheV91cmwsCiAgICAgICAgICAgICAgICB0eXBlX25hbWU6IGZpcnN0SXRlbS52b2RfY2xhc3MKICAgICAgICAgICAgfTsKICAgICAgICB9CiAgICAgICAgCiAgICAgICAgcmV0dXJuIHt9OwogICAgfSwKICAgIAogICAg5pCc57SiOiBhc3luYyBmdW5jdGlvbiAoKSB7CiAgICAgICAgaWYgKCF0aGlzLmhvc3QpIHJldHVybiBzZXRSZXN1bHQoW10pOwogICAgICAgIGxldCB1cmwgPSBgJHt0aGlzLmhvc3R9L3B1YmxpYy8/c2VydmljZT1BcHAuTW92LlNlYXJjaFZvZCZrZXk9JHtlbmNvZGVVUklDb21wb25lbnQodGhpcy5LRVkpfWA7CiAgICAgICAgbGV0IHJlc3AgPSBhd2FpdCBfZmV0Y2godXJsLCB7IGhlYWRlcnM6IHRoaXMuaGVhZGVycyB9KTsKICAgICAgICBsZXQgdGV4dCA9IGF3YWl0IHJlc3AudGV4dCgpOwogICAgICAgIGlmICh0ZXh0LnN0YXJ0c1dpdGgoJ++7vycpKSB0ZXh0ID0gdGV4dC5zdWJzdHJpbmcoMSk7CiAgICAgICAgbGV0IGRhdGEgPSBKU09OLnBhcnNlKHRleHQpOwogICAgICAgIGxldCB2aWRlb3MgPSAoZGF0YS5EYXRhIHx8IFtdKS5tYXAoaSA9PiAoewogICAgICAgICAgICB0aXRsZTogaS52b2RfbmFtZSwKICAgICAgICAgICAgdXJsOiBgJHt0aGlzLmhvc3R9L3B1YmxpYy8/c2VydmljZT1BcHAuTW92LkdldE9ubGluZU12QnlJZCZ2b2RpZD0ke2kudm9kX2lkfWAsCiAgICAgICAgICAgIGRlc2M6IGkudm9kX3JlbWFya3MsCiAgICAgICAgICAgIHBpY191cmw6IGkudm9kX3BpYywKICAgICAgICAgICAgdm9kX3llYXI6IGkudm9kX3llYXIsCiAgICAgICAgICAgIGNvbnRlbnQ6IGkudm9kX2NvbnRlbnQKICAgICAgICB9KSk7CiAgICAgICAgcmV0dXJuIHNldFJlc3VsdCh2aWRlb3MpOwogICAgfSwKICAgIAogICAgbGF6eTogYXN5bmMgZnVuY3Rpb24gKCkgewogICAgICAgIGxldCBqeCA9IDA7CiAgICAgICAgbGV0IHVybCA9ICcnOwogICAgICAgIGxldCB1YSA9ICdjb20uZ2prai56eHlzZHEvMS4xLjAgKExpbnV4O0FuZHJvaWQgMTIpIEV4b1BsYXllckxpYi8yLjEyLjMnOwogICAgICAgIGxldCBpZCA9IHRoaXMuaW5wdXQ7CiAgICAgICAgaWYgKGlkLm1hdGNoKC9eaHR0cHM/OlwvXC8uKlwuKG0zdTh8bXA0fGZsdnxta3YpL2kpKSB7CiAgICAgICAgICAgIHVybCA9IGlkOwogICAgICAgIH0gZWxzZSB7CiAgICAgICAgICAgIHRyeSB7CiAgICAgICAgICAgICAgICBsZXQgcmVzcCA9IGF3YWl0IF9mZXRjaChgJHt0aGlzLmhvc3R9L3B1YmxpYy8/c2VydmljZT1BcHAuTW92LkdldE12SlhVcmxCeVVybCZ1cmw9JHtpZH1gLCB7IGhlYWRlcnM6IHRoaXMuaGVhZGVycyB9KTsKICAgICAgICAgICAgICAgIGxldCB0ZXh0ID0gYXdhaXQgcmVzcC50ZXh0KCk7CiAgICAgICAgICAgICAgICBpZiAodGV4dC5zdGFydHNXaXRoKCfvu78nKSkgdGV4dCA9IHRleHQuc3Vic3RyaW5nKDEpOwogICAgICAgICAgICAgICAgbGV0IGRhdGEgPSBKU09OLnBhcnNlKHRleHQpOwogICAgICAgICAgICAgICAgbGV0IHJhd191cmwgPSBkYXRhLkRhdGEudXJsOwogICAgICAgICAgICAgICAgLy8g5bCd6K+V5L2/55SoQ3J5cHRvSlPov5vooYxBRVPop6Plr4YKICAgICAgICAgICAgICAgIGlmICh0aGlzLmtleSkgewogICAgICAgICAgICAgICAgICAgIHRyeSB7CiAgICAgICAgICAgICAgICAgICAgICAgIGxldCBrZXkgPSBDcnlwdG9KUy5lbmMuVXRmOC5wYXJzZSh0aGlzLmtleSk7CiAgICAgICAgICAgICAgICAgICAgICAgIGxldCBpdiA9IENyeXB0b0pTLmVuYy5VdGY4LnBhcnNlKHRoaXMuaXYpOwogICAgICAgICAgICAgICAgICAgICAgICBsZXQgZGVjcnlwdGVkVXJsID0gQ3J5cHRvSlMuQUVTLmRlY3J5cHQocmF3X3VybCwga2V5LCB7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICBpdiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1vZGU6IENyeXB0b0pTLm1vZGUuQ0JDLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgcGFkZGluZzogQ3J5cHRvSlMucGFkLlBrY3M1CiAgICAgICAgICAgICAgICAgICAgICAgIH0pLnRvU3RyaW5nKENyeXB0b0pTLmVuYy5VdGY4KTsKICAgICAgICAgICAgICAgICAgICAgICAgdXJsID0gZGVjcnlwdGVkVXJsICYmIGRlY3J5cHRlZFVybC5zdGFydHNXaXRoKCdodHRwJykgPyBkZWNyeXB0ZWRVcmwgOiB1cmw7CiAgICAgICAgICAgICAgICAgICAgfSBjYXRjaCAoZSkgewogICAgICAgICAgICAgICAgICAgICAgICBjb25zb2xlLmVycm9yKCdBRVPop6Plr4blpLHotKU6JywgZSk7CiAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgfQogICAgICAgICAgICB9IGNhdGNoIChlKSB7CiAgICAgICAgICAgICAgICBpZiAoLyg/Ond3d1wuaXFpeWl8dlwucXF8dlwueW91a3V8d3d3XC5tZ3R2fHd3d1wuYmlsaWJpbGkpXC5jb20vLnRlc3QoaWQpKSB7CiAgICAgICAgICAgICAgICAgICAgdXJsID0gaWQ7CiAgICAgICAgICAgICAgICAgICAganggPSAxOwogICAgICAgICAgICAgICAgICAgIHVhID0gTU9CSUxFX1VBOwogICAgICAgICAgICAgICAgfQogICAgICAgICAgICB9CiAgICAgICAgfQogICAgICAgIAogICAgICAgIHJldHVybiB7CiAgICAgICAgICAgIHBhcnNlOiBqeCwKICAgICAgICAgICAgdXJsLAogICAgICAgICAgICBoZWFkZXI6IHsgJ1VzZXItQWdlbnQnOiB1YSB9CiAgICAgICAgfTsKICAgIH0KfTs= \ No newline at end of file diff --git "a/spider/js/\351\205\267\347\210\261\346\274\253\347\224\273[\347\224\273].js" "b/spider/js/\351\205\267\347\210\261\346\274\253\347\224\273[\347\224\273].js" new file mode 100644 index 00000000..73f8ff72 --- /dev/null +++ "b/spider/js/\351\205\267\347\210\261\346\274\253\347\224\273[\347\224\273].js" @@ -0,0 +1,107 @@ +/* +@header({ + searchable: 2, + filterable: 0, + quickSearch: 0, + title: '酷爱漫画', + author: 'EylinSir', + '类型': '漫画', + logo: 'https://www.kuimh.com/static/images/favicon.ico', + lang: 'ds' +}) +*/ + +var rule = { + 类型: '漫画', + author: 'EylinSir', + title: '酷爱漫画', + host: 'https://www.kuimh.com', + logo: 'https://www.kuimh.com/static/images/favicon.ico', + searchUrl: '/search?keyword=**&page=fypage', + url: '/booklist?tag=fyfilter&area=fyclass&end=fyfilter&page=fypage', + searchable: 2, + quickSearch: 0, + timeout: 5000, + play_parse: true, + headers: { + 'User-Agent': 'PC_UA', + 'Referer': 'https://www.kuimh.com' + }, + class_name: '国产&日本&韩国&欧美&其他', + class_url: '1&2&3&5&7', + filter: { + "1": [ + {"key":"tag","name":"题材","value":[{"n":"全部","v":"全部"},{"n":"恋爱","v":"恋爱"},{"n":"古风","v":"古风"},{"n":"校园","v":"校园"},{"n":"奇幻","v":"奇幻"},{"n":"大女主","v":"大女主"},{"n":"治愈","v":"治愈"},{"n":"穿越","v":"穿越"},{"n":"励志","v":"励志"},{"n":"爆笑","v":"爆笑"},{"n":"萌系","v":"萌系"},{"n":"玄幻","v":"玄幻"},{"n":"日常","v":"日常"},{"n":"都市","v":"都市"},{"n":"彩虹","v":"彩虹"},{"n":"灵异","v":"灵异"},{"n":"悬疑","v":"悬疑"},{"n":"少年","v":"少年"}]}, + {"key":"end","name":"状态","value":[{"n":"全部","v":"-1&last=-1"},{"n":"最新","v":"-1&last=1"},{"n":"连载","v":"0&last=-1"},{"n":"完结","v":"1&last=-1"}]} + ] + }, + + parseList: function(html) { + return pdfa(html, '.mh-item').map(it => { + let pic = pdfh(it, 'img&&data-src'); + if (!pic) pic = (pdfh(it, 'html').match(/https?:\/\/[^"']+\.(?:jpg|png|jpeg)(?:\/\d+)?/i) || [])[0]; + return { + title: pdfh(it, '.title a&&Text'), + pic_url: pic, + desc: pdfh(it, '.chapter&&Text'), + url: pdfh(it, 'a&&href') + }; + }); + }, + + 推荐: async function(tid, pg, filter, extend) { + return await this.一级('1', 1, filter, {}); + }, + + 一级: async function(tid, pg, filter, extend) { + let url = `${this.host}/booklist?tag=${extend.tag || '全部'}&area=${tid}&end=${extend.end || '-1&last=-1'}&page=${pg}`; + let html = await request(url, { headers: this.headers }); + return setResult(this.parseList(html)); + }, + + 二级: async function(ids) { + let vid = ids[0]; + let url = this.input; + let html = await request(url); + let tabs = pdfa(html, '#detail-list-select li'); + let playUrls = tabs.map(it => { + let name = pdfh(it, 'a&&Text'); + let u = pdfh(it, 'a&&href'); + return name.trim() + '$' + u; + }).join('#'); + return { + vod_name: pdfh(html, '.info h1&&Text').trim() || "未知", + vod_pic: pdfh(html, '.cover img&&src'), + vod_content: pdfh(html, '.content&&Text').trim(), + vod_play_from: "酷爱漫画", + vod_play_url: playUrls + "$$$" + playUrls + }; + }, + + 搜索: async function(wd, quick, pg) { + let url = this.input; + let html = await request(url, { headers: this.headers }); + return setResult(this.parseList(html)); + }, + + lazy: async function () { + let { input } = this; + let url = input.startsWith('http') ? input : this.host + input; + let html = await request(url, { headers: { 'Referer': url } }); + let pattern = /(https?:\/\/[^"'\s<>]+\.(?:jpg|png|jpeg|webp)(?:\/\d+)?)/gi; + let matches = html.match(pattern) || []; + let imgs = []; + matches.forEach(src => { + if (/grey\.gif|logo|icon|tu\.petatt\.cn/.test(src)) return; + src = src.replace(/\\/g, '/'); + if (!imgs.includes(src)) imgs.push(src); + }); + return { + parse: 0, + url: imgs.length ? 'pics://' + imgs.join('&&') : url, + header: { 'Referer': url } + }; + } +}; +// 自动填充其他分类 +['2','3','5','7'].forEach(k => rule.filter[k] = rule.filter['1']); \ No newline at end of file diff --git "a/spider/js/\351\233\206\347\231\276\345\212\250\346\274\253[\346\274\253].js" "b/spider/js/\351\233\206\347\231\276\345\212\250\346\274\253[\346\274\253].js" index bf7373c3..dfe8b29f 100644 --- "a/spider/js/\351\233\206\347\231\276\345\212\250\346\274\253[\346\274\253].js" +++ "b/spider/js/\351\233\206\347\231\276\345\212\250\346\274\253[\346\274\253].js" @@ -4,7 +4,7 @@ filterable: 0, quickSearch: 0, title: '集百动漫', - author: 'EylinSir/251129/修复版', + author: 'EylinSir', '类型': '影视', lang: 'ds' }) @@ -12,49 +12,41 @@ var rule = { 类型: '影视', - author: 'EylinSir/251129/修复版', + author: 'EylinSir', title: '集百动漫', - host: 'http://www.jibai5.com', - url: '/vodtype/fyclass-fypage.html', + host: 'http://www.duanjux.com', + url: '/bm/fyclass/fypage.html', searchUrl: '/vodsearch/-------------.html?wd=**', homeUrl: '/', headers: {'User-Agent': 'UC_UA'}, - searchable: 1, quickSearch: 0, filterable: 0, double: true, play_parse: true, limit: 6, - class_name: '3D动漫&动漫&沙雕剧场', + searchable: 1, + quickSearch: 0, + filterable: 0, + double: true, + play_parse: true, + limit: 6, + class_name: '3D国漫&动漫&沙雕剧场', class_url: '20&21&22', lazy: async function () { - let {input, pdfa, pdfh, pd} = this - const html = JSON.parse((await request(input)).content.match(/player_.*?=(.*?) { d.push({ - title: pdfh(it, 'a&&title'), - pic_url: pd(it, 'img&&src'), - desc: pdfh(it, '.duration&&Text'), - url: pd(it, 'a&&href'), + title: pdfh(it, '.post-item-title&&Text'), + pic_url: pd(it, '.post-item-cover img&&src'), + desc: pdfh(it, '.post-item-summary&&Text'), + url: pd(it, '.post-item-img&&href'), }) }); return setResult(d) @@ -63,13 +55,13 @@ var rule = { let {input, pdfa, pdfh, pd} = this; let html = await request(input); let d = []; - let data = pdfa(html, '.boxlist li'); + let data = pdfa(html, '.post-item'); data.forEach((it) => { d.push({ - title: pdfh(it, 'a&&title'), - pic_url: pd(it, 'img&&src'), - desc: pdfh(it, '.duration&&Text'), - url: pd(it, 'a&&href'), + title: pdfh(it, '.post-item-title&&Text'), + pic_url: pd(it, '.post-item-cover img&&src'), + desc: pdfh(it, '.post-item-summary&&Text'), + url: pd(it, '.post-item-img&&href'), }) }); return setResult(d) @@ -78,23 +70,20 @@ var rule = { let {input, pdfa, pdfh, pd} = this; let html = await request(input); let VOD = {}; - VOD.vod_name = pdfh(html, 'h2&&Text'); - VOD.vod_content = pdfh(html, '.juqing.mbyc&&Text'); - let playlist = pdfa(html, '.dslist-group') - let tabs = pdfa(html, '.panel-default&&.panel-heading'); + const postContent = pdfa(html, '.post-content')[0]; + const contentText = pdfh(postContent, 'Text'); + const nameMatch = contentText.match(/剧名:(.*?)「/); + VOD.vod_name = nameMatch ? nameMatch[1] : pdfh(html, 'title&&Text').replace(/_.*/, ''); + VOD.vod_content = pdfh(postContent, 'Text'); + const playListDiv = pdfa(html, '.block-wrap#divTags')[0]; + const playItems = pdfa(playListDiv, 'ul li a'); let playmap = {}; - tabs.map((item, i) => { - const form = pdfh(item, 'Text') - const list = playlist[i] - const a = pdfa(list, 'body&&a') - a.map((it) => { - let title = pdfh(it, 'a&&title') - let urls = pd(it, 'a&&href', input) - if (!playmap.hasOwnProperty(form)) { - playmap[form] = []; - } - playmap[form].push(title + "$" + urls); - }); + let form = '集百动漫'; + playmap[form] = []; + playItems.forEach((it) => { + let title = pdfh(it, 'a&&title'); + let urls = pd(it, 'a&&href', input); + playmap[form].push(title + "$" + urls); }); VOD.vod_play_from = Object.keys(playmap).join('$$$'); const urls = Object.values(playmap); @@ -108,16 +97,16 @@ var rule = { let {input, pdfa, pdfh, pd} = this; let html = await request(input); let d = []; - let data = pdfa(html, '.boxlist li'); + let data = pdfa(html, '.post-item'); data.forEach((it) => { d.push({ - title: pdfh(it, 'a&&title'), - pic_url: pd(it, 'img&&src'), - desc: pdfh(it, '.duration&&Text'), - url: pd(it, 'a&&href'), - content: pdfh(it, '.list-content&&Text'), + title: pdfh(it, '.post-item-title&&Text'), + pic_url: pd(it, '.post-item-cover img&&src'), + desc: pdfh(it, '.post-item-summary&&Text'), + url: pd(it, '.post-item-img&&href'), + content: pdfh(it, '.post-item-summary&&Text'), }) }); return setResult(d) } -} +} \ No newline at end of file diff --git "a/spider/js/\351\233\252\350\212\261\347\224\265\345\275\261[\347\243\201].js" "b/spider/js/\351\233\252\350\212\261\347\224\265\345\275\261[\347\243\201].js" new file mode 100644 index 00000000..061397a5 --- /dev/null +++ "b/spider/js/\351\233\252\350\212\261\347\224\265\345\275\261[\347\243\201].js" @@ -0,0 +1,14 @@ +/* +@header({ + searchable: 2, + filterable: 0, + quickSearch: 0, + title: '雪花电影', + author: 'EylinSir', + '类型': '影视', + logo: 'https://n3300.com/favicon.ico', + lang: 'ds' +}) +*/ + +dmFyIHJ1bGUgPSB7CiAgICDnsbvlnos6ICflvbHop4YnLAogICAgYXV0aG9yOiAnRXlsaW5TaXInLAogICAgdGl0bGU6ICfpm6roirHnlLXlvbEnLAogICAgaG9zdDogJ2h0dHBzOi8vbjMzMDAuY29tJywKICAgIHVybDogJy9la090eEVCYWZORDVDaHRtbC9meWNsYXNzX2Z5cGFnZS5odG1sJywKICAgIHNlYXJjaFVybDogJy9wbHVzL3NlYXJjaC5waHA/a3d0eXBlPTAma2V5d29yZD0qKicsCiAgICBsb2dvOiAnaHR0cHM6Ly9uMzMwMC5jb20vZmF2aWNvbi5pY28nLAogICAgc2VhcmNoYWJsZTogMiwKICAgIHF1aWNrU2VhcmNoOiAwLAogICAgdGltZW91dDogNTAwMCwKICAgIGhlYWRlcnM6IHsgJ1VzZXItQWdlbnQnOiAnUENfVUEnIH0sCiAgICBjbGFzc19uYW1lOiAn5Yqo5L2c54mHJuWWnOWJp+eJhybniLHmg4XniYcm56eR5bm754mHJuWJp+aDheeJhybmgqznlpHniYcm5oiY5LqJ54mHJuaBkOaAlueJhybngb7pmr7niYcm6L+e57ut5YmnJuWKqOa8qybnu7zoibrniYcm6L+e6L295Yqo5ryrJywKICAgIGNsYXNzX3VybDogJ2Rvbmd6dW8vbGlzdCZ4aWp1L2xpc3QmYWlxaW5nL2xpc3Qma2VodWFuL2xpc3QmanVxaW5nL2xpc3QmeHVhbm5pYW4vbGlzdCZ6aGFuemhlbmcvbGlzdCZrb25nYnUvbGlzdCZ6YWluYW4vbGlzdCZsaWFueHVqdS9saXN0JmRvbmdtYW4vbGlzdCZ6b25neWlqaWVtdS9saXN0JmxpYW56YWlkb25nbWFuL2xpc3RfMjQnLAoKICAgIGNsZWFuVGl0bGU6IGZ1bmN0aW9uKHN0cikgewogICAgICAgIGlmICghc3RyKSByZXR1cm4gJyc7CiAgICAgICAgcmV0dXJuIHN0ci5yZXBsYWNlKC8oSER8QkR8RFZEfDcyMFB8MTA4MFB8NEt86JOd5YWJfOWbveivrXzoi7Hor6186Z+p6K+tfOaXpeivrXznsqTor6185Lit5a2XfOWPjOWtl3zkuK3oi7EpLiokL2ksICcnKS50cmltKCk7CiAgICB9LAoKICAgIGxhenk6IGFzeW5jIGZ1bmN0aW9uICgpIHsKICAgICAgICByZXR1cm4geyB1cmw6IHRoaXMuaW5wdXQsIHBhcnNlOiAwIH07CiAgICB9LAogICAgCiAgICDmjqjojZA6IGFzeW5jIGZ1bmN0aW9uKHRpZCwgcGcsIGZpbHRlciwgZXh0ZW5kKSB7CiAgICAgICAgcmV0dXJuIGF3YWl0IHRoaXMu5LiA57qnKHRpZCwgcGcsIGZpbHRlciwge30pOwogICAgfSwKICAgIAogICAg5LiA57qnOiBhc3luYyBmdW5jdGlvbih0aWQsIHBnLCBmaWx0ZXIsIGV4dGVuZCkgewogICAgICAgIGxldCBodG1sID0gYXdhaXQgcmVxdWVzdCh0aGlzLmlucHV0KTsKICAgICAgICBsZXQgbGlzdCA9IHBkZmEoaHRtbCwgJy51bC1pbWd0eHQyIGxpJyk7CiAgICAgICAgbGV0IGQgPSBsaXN0Lm1hcChpdCA9PiB7CiAgICAgICAgICAgIGxldCB0aXRsZSA9IHJ1bGUuY2xlYW5UaXRsZShwZGZoKGl0LCAnaDMgYSYmVGV4dCcpKTsKICAgICAgICAgICAgbGV0IHJlbWFyayA9IHBkZmgoaXQsICdoMyBlbSYmVGV4dCcpOwogICAgICAgICAgICByZXR1cm4gewogICAgICAgICAgICAgICAgdGl0bGU6IHRpdGxlLAogICAgICAgICAgICAgICAgcGljX3VybDogcGQoaXQsICdpbWcmJnNyYycsIHJ1bGUuaG9zdCksCiAgICAgICAgICAgICAgICBkZXNjOiByZW1hcmssCiAgICAgICAgICAgICAgICB1cmw6IHBkKGl0LCAnLnBpYyBhJiZocmVmJywgcnVsZS5ob3N0KSwKICAgICAgICAgICAgICAgIGNvbnRlbnQ6IHJlbWFyawogICAgICAgICAgICB9OwogICAgICAgIH0pOwogICAgICAgIHJldHVybiBzZXRSZXN1bHQoZCk7CiAgICB9LAogICAgCiAgICDkuoznuqc6IGFzeW5jIGZ1bmN0aW9uKGlkcykgewogICAgICAgIGxldCBodG1sID0gYXdhaXQgcmVxdWVzdCh0aGlzLmlucHV0KTsKICAgICAgICBsZXQgdHh0SHRtbCA9IHBkZmgoaHRtbCwgJy5tLXRleHQxIC50eHQnKTsKICAgICAgICBsZXQgY29udGVudCA9IHR4dEh0bWwubWF0Y2goL+KXjueugFtcc1xTXSo/5LuLKFtcc1xTXSo/KeKXjuW9seeJh+aIquWbvi8pPy5bMV0gPz8gdHh0SHRtbDsKICAgICAgICBjb250ZW50ID0gY29udGVudC5yZXBsYWNlKC88W14+XSs+fCZuYnNwOy9nLCAnJykudHJpbSgpOwogICAgICAgIGxldCB2b2RfcGxheV91cmwgPSBwZGZhKGh0bWwsICcuYm90IHVsIGxpIGEnKS5tYXAoaXQgPT4gewogICAgICAgICAgICBsZXQgaHJlZiA9IHBkZmgoaXQsICdhJiZocmVmJyk7CiAgICAgICAgICAgIGlmICghaHJlZi5zdGFydHNXaXRoKCdtYWduZXQnKSkgcmV0dXJuIG51bGw7CiAgICAgICAgICAgIGxldCBuYW1lID0gaHJlZi5tYXRjaCgvJmRuPShbXiZdKykvKT8uWzFdIHx8IHBkZmgoaXQsICdhJiZUZXh0JykucmVwbGFjZSgvXm1hZ25ldDouKi8sICcnKTsKICAgICAgICAgICAgdHJ5IHsgbmFtZSA9IGRlY29kZVVSSUNvbXBvbmVudChuYW1lKTsgfSBjYXRjaCB7fQogICAgICAgICAgICBuYW1lID0gbmFtZS5yZXBsYWNlKC9cWy4qP1xdfOOAkC4qP+OAkXx3d3dcLlxTKy9naSwgJycpCiAgICAgICAgICAgICAgICAgICAgICAgLnJlcGxhY2UoL1wuKG1wNHxta3Z8YXZpKSQvaSwgJycpCiAgICAgICAgICAgICAgICAgICAgICAgLnRyaW0oKSB8fCAn56OB5Yqb6ZO+5o6lJzsKICAgICAgICAgICAgcmV0dXJuIG5hbWUgKyAnJCcgKyBocmVmOwogICAgICAgIH0pLmZpbHRlcihCb29sZWFuKTsKICAgICAgICBjb25zdCBnZXRJbmZvID0gKGspID0+IHR4dEh0bWwubWF0Y2gobmV3IFJlZ0V4cChg4peOJHtrfSguKj8pPGJyPmApKT8uWzFdPy5yZXBsYWNlKC8mbmJzcDsvZywgJycpLnRyaW0oKSB8fCAnJzsKICAgICAgICByZXR1cm4gewogICAgICAgICAgICB2b2RfbmFtZTogcnVsZS5jbGVhblRpdGxlKHBkZmgoaHRtbCwgJy5tLXRleHQxIGgxJiZUZXh0JykpLAogICAgICAgICAgICB2b2RfcGljOiBwZChodG1sLCAnLm0tdGV4dDEgLnR4dCBpbWcmJnNyYycsIHJ1bGUuaG9zdCksCiAgICAgICAgICAgIHZvZF9kaXJlY3RvcjogZ2V0SW5mbygn5a+844CA44CA5ryUJyksCiAgICAgICAgICAgIHZvZF9hY3RvcjogZ2V0SW5mbygn5Li744CA44CA5ryUJykucmVwbGFjZSgvPGJyPi9nLCAnICcpLAogICAgICAgICAgICB2b2RfdHlwZTogZ2V0SW5mbygn57G744CA44CA5YirJyksCiAgICAgICAgICAgIHZvZF9hcmVhOiBnZXRJbmZvKCfkuqfjgIDjgIDlnLAnKSwKICAgICAgICAgICAgdm9kX3llYXI6IGdldEluZm8oJ+W5tOOAgOOAgOS7oycpLAogICAgICAgICAgICB2b2RfY29udGVudDogY29udGVudCwKICAgICAgICAgICAgdm9kX3BsYXlfZnJvbTogdm9kX3BsYXlfdXJsLmxlbmd0aCA/ICfno4HlipsnIDogJ+aXoOi1hOa6kCcsCiAgICAgICAgICAgIHZvZF9wbGF5X3VybDogdm9kX3BsYXlfdXJsLmpvaW4oJyMnKQogICAgICAgIH07CiAgICB9LAogICAgCiAgICDmkJzntKI6IGFzeW5jIGZ1bmN0aW9uICgpIHsKICAgICAgICBsZXQgaHRtbCA9IGF3YWl0IHJlcXVlc3QodGhpcy5pbnB1dCk7CiAgICAgICAgbGV0IGQgPSBwZGZhKGh0bWwsICcudWwtaW1ndHh0MiBsaScpLm1hcChpdCA9PiB7CiAgICAgICAgICAgIGxldCB0aXRsZSA9IHJ1bGUuY2xlYW5UaXRsZShwZGZoKGl0LCAnaDMgYSYmVGV4dCcpKTsKICAgICAgICAgICAgcmV0dXJuIHsKICAgICAgICAgICAgICAgIHRpdGxlOiB0aXRsZSwKICAgICAgICAgICAgICAgIGltZzogcGQoaXQsICdpbWcmJnNyYycsIHJ1bGUuaG9zdCksCiAgICAgICAgICAgICAgICBkZXNjOiBwZGZoKGl0LCAnaDMgZW0mJlRleHQnKSwKICAgICAgICAgICAgICAgIHVybDogcGQoaXQsICcucGljIGEmJmhyZWYnLCBydWxlLmhvc3QpCiAgICAgICAgICAgIH07CiAgICAgICAgfSk7CiAgICAgICAgcmV0dXJuIHNldFJlc3VsdChkKTsKICAgIH0KfTs= \ No newline at end of file diff --git "a/spider/js/\351\237\263\344\271\220\350\201\232\345\220\210[\345\220\254].js" "b/spider/js/\351\237\263\344\271\220\350\201\232\345\220\210[\345\220\254].js" new file mode 100644 index 00000000..94802096 --- /dev/null +++ "b/spider/js/\351\237\263\344\271\220\350\201\232\345\220\210[\345\220\254].js" @@ -0,0 +1,14 @@ +/* +@header({ + searchable: 1, + filterable: 1, + quickSearch: 1, + title: '音乐聚合', + author: 'EylinSir', + '类型': '音乐', + logo: 'https://pic.5577.com/up/2021-9/202198191801219.png', + lang: 'ds' +}) +*/ + +dmFyIHJ1bGUgPSB7CiAgICDnsbvlnos6ICfpn7PkuZAnLAogICAgYXV0aG9yOiAnRXlsaW5TaXInLAogICAgdGl0bGU6ICfpn7PkuZDogZrlkIgnLAogICAgaG9zdDogJ2h0dHBzOi8vbXVzaWMtZGwuc2F5cXouY29tJywKICAgIGxvZ286ICdodHRwczovL3BpYy41NTc3LmNvbS91cC8yMDIxLTkvMjAyMTk4MTkxODAxMjE5LnBuZycsCiAgICBoZWFkZXJzOiB7CiAgICAgICAgJ1VzZXItQWdlbnQnOiBNT0JJTEVfVUEKICAgIH0sCiAgICBzZWFyY2hhYmxlOiAxLAogICAgcXVpY2tTZWFyY2g6IDEsCiAgICBmaWx0ZXJhYmxlOiAxLAogICAgdGltZW91dDogMTAwMDAsCiAgICBwbGF5X3BhcnNlOiB0cnVlLAogICAgY2xhc3NfbmFtZTogJ+e9keaYk+S6kemfs+S5kCZRUemfs+S5kCbphbfmiJHpn7PkuZAnLAogICAgY2xhc3NfdXJsOiAnbmV0ZWFzZSZxcSZrdXdvJywKICAgIHNlYXJjaFVybDogJy9hcGkvP3R5cGU9YWdncmVnYXRlU2VhcmNoJmtleXdvcmQ9KiombGltaXQ9MTAmcGFnZT0xJywKICAgIHR5cGVzOiBbCiAgICAgICAgeyBuYW1lOiAnSGktUmVz6Z+z6LSoJywgdmFsdWU6ICdmbGFjMjRiaXQnIH0sCiAgICAgICAgeyBuYW1lOiAn5peg5o2f6Z+z6LSoJywgdmFsdWU6ICdmbGFjJyB9LAogICAgICAgIHsgbmFtZTogJ+mrmOWTgemfs+i0qCcsIHZhbHVlOiAnMzIwaycgfSwKICAgICAgICB7IG5hbWU6ICfmoIflh4bpn7PotKgnLCB2YWx1ZTogJzEyOGsnIH0KICAgIF0sCiAgICBwbGF0Zm9ybXM6IFsnbmV0ZWFzZScsICdxcScsICdrdXdvJ10sCiAgICBwbGF0Zm9ybU5hbWVNYXA6IHsKICAgICAgICBuZXRlYXNlOiAn572R5piT5LqR6Z+z5LmQJywKICAgICAgICBxcTogJ1FR6Z+z5LmQJywKICAgICAgICBrdXdvOiAn6YW35oiR6Z+z5LmQJwogICAgfSwKCiAgICDkuIDnuqc6IGFzeW5jIGZ1bmN0aW9uICh0aWQsIHBnLCBmaWx0ZXIsIGV4dGVuZCkgewogICAgICAgIGxldCByZXN1bHQgPSBbXTsKICAgICAgICBsZXQgdGFyZ2V0UGxhdGZvcm1zID0gdGlkICYmIHRoaXMucGxhdGZvcm1zLmluY2x1ZGVzKHRpZCkgPyBbdGlkXSA6IHRoaXMucGxhdGZvcm1zOwogICAgICAgIGZvciAobGV0IHBsYXRmb3JtIG9mIHRhcmdldFBsYXRmb3JtcykgewogICAgICAgICAgICB0cnkgewogICAgICAgICAgICAgICAgbGV0IGRhdGEgPSBKU09OLnBhcnNlKGF3YWl0IHJlcXVlc3QoYCR7dGhpcy5ob3N0fS9hcGkvP3NvdXJjZT0ke3BsYXRmb3JtfSZ0eXBlPXRvcGxpc3RzYCwgeyBoZWFkZXJzOiB0aGlzLmhlYWRlcnMgfSkpOwogICAgICAgICAgICAgICAgcmVzdWx0LnB1c2goLi4uKGRhdGE/LmNvZGUgPT09IDIwMCAmJiBkYXRhLmRhdGE/Lmxpc3QgPyAKICAgICAgICAgICAgICAgICAgICBkYXRhLmRhdGEubGlzdC5tYXAoaXRlbSA9PiAoewogICAgICAgICAgICAgICAgICAgICAgICB0aXRsZTogaXRlbS5uYW1lLAogICAgICAgICAgICAgICAgICAgICAgICB1cmw6IGAke3BsYXRmb3JtfV8ke2l0ZW0uaWR9YCwKICAgICAgICAgICAgICAgICAgICAgICAgZGVzYzogaXRlbS51cGRhdGVGcmVxdWVuY3ksCiAgICAgICAgICAgICAgICAgICAgICAgIHBpY191cmw6IGl0ZW0ucGljCiAgICAgICAgICAgICAgICAgICAgfSkpIDogW10pKTsKICAgICAgICAgICAgfSBjYXRjaCAoZXJyKSB7CiAgICAgICAgICAgICAgICBjb25zb2xlLmVycm9yKGVycik7CiAgICAgICAgICAgIH0KICAgICAgICB9CiAgICAgICAgcmV0dXJuIHNldFJlc3VsdChyZXN1bHQpOwogICAgfSwKCiAgICDkuoznuqc6IGFzeW5jIGZ1bmN0aW9uICgpIHsKICAgICAgICBsZXQgW3BsYXRmb3JtLCByZWFsSWRdID0gdGhpcy5vcklkLnNwbGl0KCdfJyk7CiAgICAgICAgaWYgKCFwbGF0Zm9ybSB8fCAhcmVhbElkKSByZXR1cm4ge307CiAgICAgICAgbGV0IHNvbmdMaXN0ID0gW107CiAgICAgICAgbGV0IHZvZF9uYW1lID0gdGhpcy5vcklkOwogICAgICAgIGxldCB2b2RfZGVzYyA9ICcnOwogICAgICAgIHRyeSB7CiAgICAgICAgICAgIGxldCByZXMgPSBhd2FpdCByZXF1ZXN0KGAke3RoaXMuaG9zdH0vYXBpLz9zb3VyY2U9JHtwbGF0Zm9ybX0maWQ9JHtyZWFsSWR9JnR5cGU9dG9wbGlzdGAsIHsgaGVhZGVyczogdGhpcy5oZWFkZXJzIH0pOwogICAgICAgICAgICBsZXQgc29uZ0RhdGEgPSBKU09OLnBhcnNlKHJlcyk7CiAgICAgICAgICAgIGlmIChzb25nRGF0YT8uY29kZSA9PT0gMjAwICYmIHNvbmdEYXRhLmRhdGE/Lmxpc3QpIHsKICAgICAgICAgICAgICAgIHNvbmdMaXN0ID0gc29uZ0RhdGEuZGF0YS5saXN0OwogICAgICAgICAgICAgICAgdHJ5IHsKICAgICAgICAgICAgICAgICAgICBsZXQgdG9wTGlzdHNSZXMgPSBhd2FpdCByZXF1ZXN0KGAke3RoaXMuaG9zdH0vYXBpLz9zb3VyY2U9JHtwbGF0Zm9ybX0mdHlwZT10b3BsaXN0c2AsIHsgaGVhZGVyczogdGhpcy5oZWFkZXJzIH0pOwogICAgICAgICAgICAgICAgICAgIGxldCB0b3BMaXN0c0RhdGEgPSBKU09OLnBhcnNlKHRvcExpc3RzUmVzKTsKICAgICAgICAgICAgICAgICAgICBsZXQgY3VycmVudExpc3QgPSB0b3BMaXN0c0RhdGE/LmNvZGUgPT09IDIwMCAmJiB0b3BMaXN0c0RhdGEuZGF0YT8ubGlzdCA/IAogICAgICAgICAgICAgICAgICAgICAgICB0b3BMaXN0c0RhdGEuZGF0YS5saXN0LmZpbmQoaXRlbSA9PiBpdGVtLmlkID09PSByZWFsSWQpIDogbnVsbDsKICAgICAgICAgICAgICAgICAgICBpZiAoY3VycmVudExpc3QpIHsKICAgICAgICAgICAgICAgICAgICAgICAgdm9kX25hbWUgPSBjdXJyZW50TGlzdC5uYW1lOwogICAgICAgICAgICAgICAgICAgICAgICB2b2RfZGVzYyA9IGN1cnJlbnRMaXN0LnVwZGF0ZUZyZXF1ZW5jeSB8fCAnJzsKICAgICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICB9IGNhdGNoIChlcnIpIHsKICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgfSBlbHNlIHsKICAgICAgICAgICAgICAgIHJlcyA9IGF3YWl0IHJlcXVlc3QoYCR7dGhpcy5ob3N0fS9hcGkvP3NvdXJjZT0ke3BsYXRmb3JtfSZpZD0ke3JlYWxJZH0mdHlwZT1pbmZvYCwgeyBoZWFkZXJzOiB0aGlzLmhlYWRlcnMgfSk7CiAgICAgICAgICAgICAgICBzb25nRGF0YSA9IEpTT04ucGFyc2UocmVzKTsKICAgICAgICAgICAgICAgIGlmIChzb25nRGF0YT8uY29kZSAhPT0gMjAwIHx8ICFzb25nRGF0YS5kYXRhKSByZXR1cm4ge307CiAgICAgICAgICAgICAgICBzb25nTGlzdCA9IFtzb25nRGF0YS5kYXRhXTsKICAgICAgICAgICAgICAgIHZvZF9uYW1lID0gYCR7c29uZ0RhdGEuZGF0YS5uYW1lfSAtICR7c29uZ0RhdGEuZGF0YS5hcnRpc3R9YDsKICAgICAgICAgICAgICAgIHZvZF9kZXNjID0gc29uZ0RhdGEuZGF0YS5hbGJ1bSB8fCAnJzsKICAgICAgICAgICAgfQogICAgICAgIH0gY2F0Y2ggKGVycikgewogICAgICAgICAgICBjb25zb2xlLmVycm9yKCfojrflj5bmlbDmja7lpLHotKU6JywgZXJyKTsKICAgICAgICAgICAgcmV0dXJuIHt9OwogICAgICAgIH0KCiAgICAgICAgaWYgKHZvZF9uYW1lID09PSB0aGlzLm9ySWQgJiYgc29uZ0xpc3QubGVuZ3RoKSB7CiAgICAgICAgICAgIHZvZF9uYW1lID0gYCR7c29uZ0xpc3RbMF0uYWxidW19IC0g5q2M5puy5YiX6KGoYDsKICAgICAgICAgICAgdm9kX2Rlc2MgPSBg5YWx5YyF5ZCrICR7c29uZ0xpc3QubGVuZ3RofSDpppbmrYzmm7JgOwogICAgICAgIH0KCiAgICAgICAgbGV0IHBsYXlfdXJsID0gc29uZ0xpc3QubWFwKHNvbmcgPT4gYCR7c29uZy5uYW1lfSAtICR7c29uZy5hcnRpc3R9JCR7cGxhdGZvcm19XyR7c29uZy5pZCB8fCByZWFsSWR9YCkuam9pbignIycpOwogICAgICAgIHJldHVybiB7CiAgICAgICAgICAgIHZvZF9pZDogdGhpcy5vcklkLAogICAgICAgICAgICB2b2RfbmFtZSwKICAgICAgICAgICAgdm9kX3BsYXlfZnJvbTogdGhpcy5wbGF0Zm9ybU5hbWVNYXBbcGxhdGZvcm1dLAogICAgICAgICAgICB2b2RfcGxheV91cmw6IHBsYXlfdXJsLAogICAgICAgICAgICB2b2RfZGVzYwogICAgICAgIH07CiAgICB9LAoKICAgIOaQnOe0ojogYXN5bmMgZnVuY3Rpb24gKCkgewogICAgICAgIGxldCB1cmwgPSB0aGlzLnNlYXJjaFVybC5yZXBsYWNlKCcqKicsIGVuY29kZVVSSUNvbXBvbmVudCh0aGlzLktFWSkpOwogICAgICAgIHRyeSB7CiAgICAgICAgICAgIGxldCBkYXRhID0gYXdhaXQgcmVxdWVzdCh1cmwsIHsKICAgICAgICAgICAgICAgIGhlYWRlcnM6IHRoaXMuaGVhZGVycywKICAgICAgICAgICAgICAgIHRpbWVvdXQ6IDEwMDAwIAogICAgICAgICAgICB9KTsKICAgICAgICAgICAgbGV0IGpzb24gPSBKU09OLnBhcnNlKGRhdGEpOwogICAgICAgICAgICBpZiAoanNvbj8uY29kZSA9PT0gMjAwICYmIGpzb24/LmRhdGE/LnJlc3VsdHMpIHsKICAgICAgICAgICAgICAgIGxldCByZXN1bHQgPSBqc29uLmRhdGEucmVzdWx0cy5tYXAoaXRlbSA9PiAoewogICAgICAgICAgICAgICAgICAgIHRpdGxlOiBgJHtpdGVtLm5hbWV9IC0gJHtpdGVtLmFydGlzdH1gLAogICAgICAgICAgICAgICAgICAgIGRlc2M6IGAke2l0ZW0ucGxhdGZvcm0gPT09ICduZXRlYXNlJyA/ICfnvZHmmJPkupEnIDogaXRlbS5wbGF0Zm9ybSA9PT0gJ3FxJyA/ICdRUScgOiAn6YW35oiRJ33pn7PkuZAgLSAke2l0ZW0uYWxidW19YCwKICAgICAgICAgICAgICAgICAgICBwaWNfdXJsOiBpdGVtLnBpYywKICAgICAgICAgICAgICAgICAgICB1cmw6IGAke2l0ZW0ucGxhdGZvcm19XyR7aXRlbS5pZH1gCiAgICAgICAgICAgICAgICB9KSk7CiAgICAgICAgICAgICAgICByZXR1cm4gc2V0UmVzdWx0KHJlc3VsdCk7CiAgICAgICAgICAgIH0KICAgICAgICB9IGNhdGNoIChlcnJvcikgewogICAgICAgICAgICBjb25zb2xlLmVycm9yKCfmkJzntKLor7fmsYLplJnor686JywgZXJyb3IpOwogICAgICAgIH0KICAgICAgICByZXR1cm4gc2V0UmVzdWx0KFtdKTsKICAgIH0sCgogICAgbGF6eTogYXN5bmMgZnVuY3Rpb24gKCkgewogICAgICAgIGxldCBbcGxhdGZvcm0sIHJlYWxJZF0gPSB0aGlzLmlucHV0LnNwbGl0KCdfJyk7CiAgICAgICAgaWYgKCFwbGF0Zm9ybSB8fCAhcmVhbElkKSB7CiAgICAgICAgICAgIHJldHVybiB7IHBhcnNlOiAwLCBwbGF5VXJsOiAnJywgdXJsOiBbXSwgaGVhZGVyOiB0aGlzLmhlYWRlcnMsIGxyYzogJycsIGNvdmVyOiAnJywgaGVpZ2h0OiA3MjAgfTsKICAgICAgICB9CiAgICAgICAgbGV0IGx5cmljID0gJyc7CiAgICAgICAgbGV0IGluZm9EYXRhID0ge307CiAgICAgICAgbGV0IFtseXJpY1JlcywgaW5mb1Jlc10gPSBhd2FpdCBQcm9taXNlLmFsbChbCiAgICAgICAgICAgIHJlcXVlc3QoYCR7dGhpcy5ob3N0fS9hcGkvP3NvdXJjZT0ke3BsYXRmb3JtfSZpZD0ke3JlYWxJZH0mdHlwZT1scmNgLCB7IGhlYWRlcnM6IHRoaXMuaGVhZGVycyB9KS5jYXRjaCgoKSA9PiAnJyksCiAgICAgICAgICAgIHJlcXVlc3QoYCR7dGhpcy5ob3N0fS9hcGkvP3NvdXJjZT0ke3BsYXRmb3JtfSZpZD0ke3JlYWxJZH0mdHlwZT1pbmZvYCwgeyBoZWFkZXJzOiB0aGlzLmhlYWRlcnMgfSkuY2F0Y2goKCkgPT4gJ3t9JykKICAgICAgICBdKTsKICAgICAgICBpZiAodHlwZW9mIGx5cmljUmVzID09PSAnc3RyaW5nJykgbHlyaWMgPSBseXJpY1JlczsKICAgICAgICB0cnkgewogICAgICAgICAgICBpbmZvRGF0YSA9IEpTT04ucGFyc2UoaW5mb1Jlcyk7CiAgICAgICAgfSBjYXRjaCAoZXJyKSB7fQogICAgICAgIGxldCBjb3ZlciA9IGluZm9EYXRhPy5jb2RlID09PSAyMDAgPyBpbmZvRGF0YS5kYXRhPy5waWMgfHwgJycgOiAnJzsKICAgICAgICBsZXQgdXJsID0gdGhpcy50eXBlcy5mbGF0TWFwKHR5cGUgPT4gW3R5cGUubmFtZSwgYCR7dGhpcy5ob3N0fS9hcGkvP3NvdXJjZT0ke3BsYXRmb3JtfSZpZD0ke3JlYWxJZH0mdHlwZT11cmwmYnI9JHt0eXBlLnZhbHVlfWBdKTsKICAgICAgICByZXR1cm4gewogICAgICAgICAgICBwYXJzZTogMCwKICAgICAgICAgICAgcGxheVVybDogJycsCiAgICAgICAgICAgIHVybCwKICAgICAgICAgICAgaGVhZGVyOiB0aGlzLmhlYWRlcnMsCiAgICAgICAgICAgIGxyYzogbHlyaWMsCiAgICAgICAgICAgIGNvdmVyLAogICAgICAgICAgICBwaWM6IGNvdmVyLAogICAgICAgICAgICBoZWlnaHQ6IDcyMAogICAgICAgIH07CiAgICB9Cn07 \ No newline at end of file diff --git "a/spider/js/\351\272\273\351\233\200\350\247\206\351\242\221[\344\274\230].js" "b/spider/js/\351\272\273\351\233\200\350\247\206\351\242\221[\344\274\230].js" new file mode 100644 index 00000000..b63b250d --- /dev/null +++ "b/spider/js/\351\272\273\351\233\200\350\247\206\351\242\221[\344\274\230].js" @@ -0,0 +1,13 @@ +/* +@header({ + searchable: 1, + filterable: 1, + quickSearch: 0, + title: '麻雀视频', + author: 'EylinSir', + '类型': '影视', + lang: 'ds' +}) +*/ + +dmFyIHJ1bGUgPSB7CiAgICDnsbvlnos6ICflvbHop4YnLAogICAgYXV0aG9yOiAnRXlsaW5TaXInLAogICAgdGl0bGU6ICfpurvpm4Dop4bpopEnLAogICAgaG9zdDogJ2h0dHBzOi8vd3d3Lm1xdHYuY2MnLAogICAgaG9tZVVybDogJ2h0dHBzOi8vd3d3Lm1xdHYuY2MnLAogICAgdXJsOiAnL2xpYnMvVm9kTGlzdC5hcGkucGhwP3R5cGU9ZnljbGFzcyZyYW5rPXJhbmtob3QmY2F0PSZ5ZWFyPSZhcmVhPSZwYWdlPWZ5cGFnZSZ0b2tlbj1meXRva2VuJywKICAgIHNlYXJjaFVybDogJy9saWJzL1ZvZExpc3QuYXBpLnBocD9zZWFyY2g9KiomdG9rZW49Znl0b2tlbicsCiAgICBzZWFyY2hhYmxlOiAxLAogICAgcXVpY2tTZWFyY2g6IDAsCiAgICBmaWx0ZXJhYmxlOiAxLAogICAgdGltZW91dDogMTAwMDAsCiAgICBwbGF5X3BhcnNlOiB0cnVlLAogICAga2V5OiAnTWN4b3NAbXVjaG8hbm1tZScsCiAgICBoZWFkZXJzOiB7CiAgICAgICAgJ1VzZXItQWdlbnQnOiAnTW96aWxsYS81LjAgKFdpbmRvd3MgTlQgMTAuMDsgV2luNjQ7IHg2NCkgQXBwbGVXZWJLaXQvNTM3LjM2IChLSFRNTCwgbGlrZSBHZWNrbykgQ2hyb21lLzE0Mi4wLjAuMCBTYWZhcmkvNTM3LjM2JywKICAgICAgICAnYWNjZXB0LWxhbmd1YWdlJzogJ3poLUNOLHpoO3E9MC45JywKICAgICAgICAnc2VjLWNoLXVhJzogJyJHb29nbGUgQ2hyb21lIjt2PSIxNDMiLCAiQ2hyb21pdW0iO3Y9IjE0MyIsICJOb3QgQShCcmFuZCI7dj0iMjQiJywKICAgICAgICAnY2FjaGUtY29udHJvbCc6ICduby1jYWNoZScsCiAgICAgICAgJ3ByYWdtYSc6ICduby1jYWNoZScsCiAgICAgICAgJ3NlYy1jaC11YS1tb2JpbGUnOiAnPzAnLAogICAgICAgICdzZWMtY2gtdWEtcGxhdGZvcm0nOiAnIldpbmRvd3MiJywKICAgICAgICAnc2VjLWZldGNoLXNpdGUnOiAnc2FtZS1vcmlnaW4nCiAgICB9LAogICAgCiAgICBnZXRUb2tlbjogYXN5bmMgZnVuY3Rpb24gKHBhdGgsIHJlZlBhdGggPSAnJykgewogICAgICAgIGxldCBoZWFkZXJzID0gewogICAgICAgICAgICAuLi50aGlzLmhlYWRlcnMsCiAgICAgICAgICAgICdBY2NlcHQnOiAndGV4dC9odG1sLGFwcGxpY2F0aW9uL3hodG1sK3htbCxhcHBsaWNhdGlvbi94bWw7cT0wLjksaW1hZ2UvYXZpZixpbWFnZS93ZWJwLGltYWdlL2FwbmcsKi8qO3E9MC44LGFwcGxpY2F0aW9uL3NpZ25lZC1leGNoYW5nZTt2PWIzO3E9MC43JywKICAgICAgICAgICAgJ3ByaW9yaXR5JzogJ3U9MCwgaScsCiAgICAgICAgICAgICdzZWMtZmV0Y2gtZGVzdCc6ICdkb2N1bWVudCcsCiAgICAgICAgICAgICdzZWMtZmV0Y2gtbW9kZSc6ICduYXZpZ2F0ZScsCiAgICAgICAgICAgICdzZWMtZmV0Y2gtdXNlcic6ICc/MScsCiAgICAgICAgICAgICd1cGdyYWRlLWluc2VjdXJlLXJlcXVlc3RzJzogJzEnCiAgICAgICAgfTsKICAgICAgICBpZiAocmVmUGF0aCkgaGVhZGVyc1sncmVmZXJlciddID0gYCR7dGhpcy5ob3N0fSR7cmVmUGF0aH1gOwogICAgICAgIGxldCByZXNwID0gYXdhaXQgX2ZldGNoKGAke3RoaXMuaG9zdH0ke3BhdGh9YCwgeyBoZWFkZXJzIH0pOwogICAgICAgIGxldCBodG1sID0gYXdhaXQgcmVzcC50ZXh0KCk7CiAgICAgICAgbGV0IG1hdGNoID0gaHRtbC5tYXRjaCgvd2luZG93XC5wYWdlaWRccz89XHM/JyguKj8pJzsvaSk7CiAgICAgICAgbGV0IHBhZ2VJZCA9IG1hdGNoID8gbWF0Y2hbMV0gOiAnJzsKICAgICAgICByZXR1cm4gdGhpcy5lbmNvZGVEYXRhKHBhZ2VJZCk7CiAgICB9LAoKICAgIGVuY29kZURhdGE6IGZ1bmN0aW9uIChkYXRhKSB7CiAgICAgICAgbGV0IGpzb25TdHIgPSBKU09OLnN0cmluZ2lmeShkYXRhKTsKICAgICAgICBsZXQgYjY0XzEgPSBidG9hKHVuZXNjYXBlKGVuY29kZVVSSUNvbXBvbmVudChqc29uU3RyKSkpOwogICAgICAgIGxldCB4b3JfcmVzdWx0ID0gJyc7CiAgICAgICAgbGV0IGtleUxlbiA9IHRoaXMua2V5Lmxlbmd0aDsKICAgICAgICBmb3IgKGxldCBpID0gMDsgaSA8IGI2NF8xLmxlbmd0aDsgaSsrKSB7CiAgICAgICAgICAgIGxldCBjaGFyQ29kZSA9IGI2NF8xLmNoYXJDb2RlQXQoaSkgXiB0aGlzLmtleS5jaGFyQ29kZUF0KGkgJSBrZXlMZW4pOwogICAgICAgICAgICB4b3JfcmVzdWx0ICs9IFN0cmluZy5mcm9tQ2hhckNvZGUoY2hhckNvZGUpOwogICAgICAgIH0KICAgICAgICBsZXQgYjY0XzIgPSBidG9hKHVuZXNjYXBlKGVuY29kZVVSSUNvbXBvbmVudCh4b3JfcmVzdWx0KSkpOwogICAgICAgIHJldHVybiBlbmNvZGVVUklDb21wb25lbnQoYjY0XzIpOwogICAgfSwKCiAgICBkZWNvZGVEYXRhOiBmdW5jdGlvbiAoZW5jb2RlZFN0cikgewogICAgICAgIHRyeSB7CiAgICAgICAgICAgIGxldCBiYXNlNjREZWNvZGUgPSAoc3RyKSA9PiB7CiAgICAgICAgICAgICAgICB0cnkgewogICAgICAgICAgICAgICAgICAgIHJldHVybiBkZWNvZGVVUklDb21wb25lbnQoZXNjYXBlKGF0b2Ioc3RyKSkpOwogICAgICAgICAgICAgICAgfSBjYXRjaCAoZSkgewogICAgICAgICAgICAgICAgICAgIHJldHVybiAnJzsKICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgfTsKICAgICAgICAgICAgCiAgICAgICAgICAgIGxldCBkZWNvZGVkU3RlcDFTdHIgPSBiYXNlNjREZWNvZGUoZW5jb2RlZFN0cik7CiAgICAgICAgICAgIGxldCB4b3JSZXN1bHQgPSAnJzsKICAgICAgICAgICAgbGV0IGtleUxlbiA9IHRoaXMua2V5Lmxlbmd0aDsKICAgICAgICAgICAgZm9yIChsZXQgaSA9IDA7IGkgPCBkZWNvZGVkU3RlcDFTdHIubGVuZ3RoOyBpKyspIHsKICAgICAgICAgICAgICAgIHhvclJlc3VsdCArPSBTdHJpbmcuZnJvbUNoYXJDb2RlKGRlY29kZWRTdGVwMVN0ci5jaGFyQ29kZUF0KGkpIF4gdGhpcy5rZXkuY2hhckNvZGVBdChpICUga2V5TGVuKSk7CiAgICAgICAgICAgIH0KICAgICAgICAgICAgCiAgICAgICAgICAgIGxldCBkZWNvZGVkU3RlcDJTdHIgPSBiYXNlNjREZWNvZGUoeG9yUmVzdWx0KTsKICAgICAgICAgICAgcmV0dXJuIEpTT04ucGFyc2UoZGVjb2RlZFN0ZXAyU3RyKTsKICAgICAgICB9IGNhdGNoIChlKSB7CiAgICAgICAgICAgIGNvbnNvbGUuZXJyb3IoJ0RlY29kZSBlcnJvcjonLCBlKTsKICAgICAgICAgICAgcmV0dXJuIHt9OwogICAgICAgIH0KICAgIH0sCiAgICAKICAgIGdldEhlYWRlcnMyOiBmdW5jdGlvbiAocmVmUGF0aCA9ICcnKSB7CiAgICAgICAgbGV0IGhlYWRlcnMgPSB7CiAgICAgICAgICAgIC4uLnRoaXMuaGVhZGVycywKICAgICAgICAgICAgJ0FjY2VwdCc6ICdhcHBsaWNhdGlvbi9qc29uLCB0ZXh0L2phdmFzY3JpcHQsICovKjsgcT0wLjAxJywKICAgICAgICAgICAgJ3ByaW9yaXR5JzogJ3U9MSwgaScsCiAgICAgICAgICAgICdzZWMtZmV0Y2gtZGVzdCc6ICdlbXB0eScsCiAgICAgICAgICAgICdzZWMtZmV0Y2gtbW9kZSc6ICdjb3JzJywKICAgICAgICAgICAgJ3gtcmVxdWVzdGVkLXdpdGgnOiAnWE1MSHR0cFJlcXVlc3QnCiAgICAgICAgfTsKICAgICAgICBpZiAocmVmUGF0aCkgaGVhZGVyc1sncmVmZXJlciddID0gYCR7dGhpcy5ob3N0fSR7cmVmUGF0aH1gOwogICAgICAgIHJldHVybiBoZWFkZXJzOwogICAgfSwKCiAgICBhcnIydm9kczogZnVuY3Rpb24gKGFycikgewogICAgICAgIHJldHVybiAoYXJyIHx8IFtdKS5tYXAoaSA9PiAoewogICAgICAgICAgICB0aXRsZTogaS50aXRsZSwKICAgICAgICAgICAgdXJsOiBgJHt0aGlzLmhvc3R9JHtpLnVybH1gLAogICAgICAgICAgICBkZXNjOiBpLnJlbWFyaywKICAgICAgICAgICAgcGljX3VybDogaS5pbWcsCiAgICAgICAgICAgIHZvZF95ZWFyOiBpLnllYXIgfHwgJycKICAgICAgICB9KSk7CiAgICB9LAogICAgCiAgICDpooTlpITnkIY6IGFzeW5jIGZ1bmN0aW9uICgpIHt9LAoKICAgIGNsYXNzX3BhcnNlOiBhc3luYyBmdW5jdGlvbiAoKSB7CiAgICAgICAgcmV0dXJuIHsKICAgICAgICAgICAgY2xhc3M6IFsKICAgICAgICAgICAgICAgIHsgJ3R5cGVfaWQnOiAnL3R5cGUvbW92aWUnLCAndHlwZV9uYW1lJzogJ+eUteW9sScgfSwKICAgICAgICAgICAgICAgIHsgJ3R5cGVfaWQnOiAnL3R5cGUvdHYnLCAndHlwZV9uYW1lJzogJ+eUteinhuWJpycgfSwKICAgICAgICAgICAgICAgIHsgJ3R5cGVfaWQnOiAnL3R5cGUvdmEnLCAndHlwZV9uYW1lJzogJ+e7vOiJuicgfSwKICAgICAgICAgICAgICAgIHsgJ3R5cGVfaWQnOiAnL3R5cGUvY3QnLCAndHlwZV9uYW1lJzogJ+WKqOa8qycgfQogICAgICAgICAgICBdLAogICAgICAgICAgICBmaWx0ZXJzOiB7fQogICAgICAgIH07CiAgICB9LAogICAgCiAgICDmjqjojZA6IGFzeW5jIGZ1bmN0aW9uICgpIHsKICAgICAgICBsZXQgdG9rZW4gPSBhd2FpdCB0aGlzLmdldFRva2VuKCcvJyk7CiAgICAgICAgbGV0IHVybCA9IGAke3RoaXMuaG9zdH0vbGlicy9Wb2RMaXN0LmFwaS5waHA/aG9tZT1pbmRleCZ0b2tlbj0ke3Rva2VufWA7CiAgICAgICAgbGV0IHJlc3AgPSBhd2FpdCBfZmV0Y2godXJsLCB7IGhlYWRlcnM6IHRoaXMuZ2V0SGVhZGVyczIoJy8nKSB9KTsKICAgICAgICBsZXQganNvbiA9IEpTT04ucGFyc2UoYXdhaXQgcmVzcC50ZXh0KCkpOwogICAgICAgIGxldCB2aWRlb3MgPSBbXTsKICAgICAgICBpZiAoanNvbi5kYXRhICYmIGpzb24uZGF0YS5tb3ZpZSkgewogICAgICAgICAgICBmb3IgKGxldCBpIG9mIGpzb24uZGF0YS5tb3ZpZSkgewogICAgICAgICAgICAgICAgdmlkZW9zLnB1c2goLi4udGhpcy5hcnIydm9kcyhpLnNob3cpKTsKICAgICAgICAgICAgfQogICAgICAgIH0KICAgICAgICByZXR1cm4gc2V0UmVzdWx0KHZpZGVvcyk7CiAgICB9LAogICAgCiAgICDkuIDnuqc6IGFzeW5jIGZ1bmN0aW9uICh0aWQsIHBnLCBmaWx0ZXIsIGV4dGVuZCkgewogICAgICAgIGxldCB0eXBlS2V5ID0gdGlkLnNwbGl0KCcvJylbMl07CiAgICAgICAgbGV0IHRva2VuID0gYXdhaXQgdGhpcy5nZXRUb2tlbih0aWQsICcvJyk7CiAgICAgICAgbGV0IHVybCA9IGAke3RoaXMuaG9zdH0vbGlicy9Wb2RMaXN0LmFwaS5waHA/dHlwZT0ke3R5cGVLZXl9JnJhbms9cmFua2hvdCZjYXQ9JnllYXI9JmFyZWE9JnBhZ2U9JHtwZ30mdG9rZW49JHt0b2tlbn1gOwogICAgICAgIGxldCByZXNwID0gYXdhaXQgX2ZldGNoKHVybCwgeyBoZWFkZXJzOiB0aGlzLmdldEhlYWRlcnMyKHRpZCkgfSk7CiAgICAgICAgbGV0IGpzb24gPSBKU09OLnBhcnNlKGF3YWl0IHJlc3AudGV4dCgpKTsKICAgICAgICBsZXQgdmlkZW9zID0gdGhpcy5hcnIydm9kcyhqc29uLmRhdGEpOwogICAgICAgIHJldHVybiBzZXRSZXN1bHQodmlkZW9zKTsKICAgIH0sCiAgICAKICAgIOS6jOe6pzogYXN5bmMgZnVuY3Rpb24gKCkgewogICAgICAgIGxldCBpZCA9IHRoaXMuaW5wdXQucmVwbGFjZShgJHt0aGlzLmhvc3R9YCwgJycpOwogICAgICAgIGxldCByZWFsSWQgPSBpZC5zcGxpdCgnLycpW2lkLnNwbGl0KCcvJykubGVuZ3RoIC0gMV07CiAgICAgICAgbGV0IHRva2VuID0gYXdhaXQgdGhpcy5nZXRUb2tlbihpZCwgJy8nKTsKICAgICAgICBsZXQgdXJsID0gYCR7dGhpcy5ob3N0fS9saWJzL1ZvZEluZm8uYXBpLnBocD90eXBlPWN0JmlkPSR7cmVhbElkfSZ0b2tlbj0ke3Rva2VufWA7CiAgICAgICAgbGV0IHJlc3AgPSBhd2FpdCBfZmV0Y2godXJsLCB7IGhlYWRlcnM6IHRoaXMuZ2V0SGVhZGVyczIoaWQpIH0pOwogICAgICAgIGxldCBqc29uID0gSlNPTi5wYXJzZShhd2FpdCByZXNwLnRleHQoKSk7CiAgICAgICAgbGV0IGRhdGEgPSBqc29uLmRhdGE7CiAgICAgICAgbGV0IHBsYXlBcGkgPSBkYXRhLnBsYXlhcGkgfHwgW107CiAgICAgICAgbGV0IHBhcnNlcyA9IHBsYXlBcGkKICAgICAgICAgICAgLmZpbHRlcihpID0+IHR5cGVvZiBpID09PSAnb2JqZWN0JyAmJiBpICE9PSBudWxsICYmIGkudXJsICYmIHR5cGVvZiBpLnVybCA9PT0gJ3N0cmluZycpCiAgICAgICAgICAgIC5tYXAoaSA9PiBpLnVybC5zdGFydHNXaXRoKCcvLycpID8gYGh0dHBzOiR7aS51cmx9YCA6IGkudXJsKQogICAgICAgICAgICAuam9pbignLCcpOwogICAgICAgIGxldCBzaG93cyA9IFtdOwogICAgICAgIGxldCBwbGF5VXJscyA9IFtdOwogICAgICAgIGlmIChkYXRhLnBsYXlpbmZvKSB7CiAgICAgICAgICAgIGZvciAobGV0IGogb2YgZGF0YS5wbGF5aW5mbykgewogICAgICAgICAgICAgICAgbGV0IHVybHMgPSBbXTsKICAgICAgICAgICAgICAgIGZvciAobGV0IGsgb2Ygai5wbGF5ZXIpIHsKICAgICAgICAgICAgICAgICAgICB1cmxzLnB1c2goYCR7ay5ub30kJHtrLnVybH1AJHtwYXJzZXN9YCk7CiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICBpZiAodXJscy5sZW5ndGggPiAwKSB7CiAgICAgICAgICAgICAgICAgICAgcGxheVVybHMucHVzaCh1cmxzLmpvaW4oJyMnKSk7CiAgICAgICAgICAgICAgICAgICAgc2hvd3MucHVzaChqLmNuc2l0ZSk7CiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgIH0KICAgICAgICB9CiAgICAgICAgCiAgICAgICAgbGV0IFZPRCA9IHsKICAgICAgICAgICAgdm9kX2lkOiBpZCwKICAgICAgICAgICAgdm9kX25hbWU6IGRhdGEudGl0bGUsCiAgICAgICAgICAgIHZvZF9waWM6IGRhdGEuaW1nLAogICAgICAgICAgICB2b2RfcmVtYXJrczogZGF0YS5yZW1hcmssCiAgICAgICAgICAgIHZvZF95ZWFyOiBkYXRhLnllYXIsCiAgICAgICAgICAgIHZvZF9hcmVhOiBkYXRhLmFyZWEsCiAgICAgICAgICAgIHZvZF9hY3RvcjogZGF0YS5hY3RvciwKICAgICAgICAgICAgdm9kX2RpcmVjdG9yOiBkYXRhLmRpcmVjdG9yLAogICAgICAgICAgICB2b2RfY29udGVudDogJycsCiAgICAgICAgICAgIHZvZF9wbGF5X2Zyb206IHNob3dzLmpvaW4oJyQkJCcpLAogICAgICAgICAgICB2b2RfcGxheV91cmw6IHBsYXlVcmxzLmpvaW4oJyQkJCcpLAogICAgICAgICAgICB0eXBlX25hbWU6ICcnCiAgICAgICAgfTsKICAgICAgICByZXR1cm4gVk9EOwogICAgfSwKICAgIAogICAg5pCc57SiOiBhc3luYyBmdW5jdGlvbiAoKSB7CiAgICAgICAgbGV0IHBhdGggPSBgL3NlYXJjaC8ke2VuY29kZVVSSUNvbXBvbmVudCh0aGlzLktFWSl9YDsKICAgICAgICBsZXQgdG9rZW4gPSBhd2FpdCB0aGlzLmdldFRva2VuKHBhdGgsICcvJyk7CiAgICAgICAgbGV0IHVybCA9IGAke3RoaXMuaG9zdH0vbGlicy9Wb2RMaXN0LmFwaS5waHA/c2VhcmNoPSR7ZW5jb2RlVVJJQ29tcG9uZW50KHRoaXMuS0VZKX0mdG9rZW49JHt0b2tlbn1gOwogICAgICAgIGxldCByZXNwID0gYXdhaXQgX2ZldGNoKHVybCwgeyBoZWFkZXJzOiB0aGlzLmdldEhlYWRlcnMyKHBhdGgpIH0pOwogICAgICAgIGxldCBqc29uID0gSlNPTi5wYXJzZShhd2FpdCByZXNwLnRleHQoKSk7CiAgICAgICAgbGV0IGRlY29kZURhdGEgPSB0aGlzLmRlY29kZURhdGEoanNvbi5kYXRhKTsKICAgICAgICBsZXQgdmlkZW9zID0gW107CiAgICAgICAgaWYgKGRlY29kZURhdGEudm9kX2FsbCkgewogICAgICAgICAgICBmb3IgKGxldCBpIG9mIGRlY29kZURhdGEudm9kX2FsbCkgewogICAgICAgICAgICAgICAgaWYgKHR5cGVvZiBpID09PSAnb2JqZWN0JyAmJiBpICE9PSBudWxsKSB7CiAgICAgICAgICAgICAgICAgICAgdmlkZW9zLnB1c2goLi4udGhpcy5hcnIydm9kcyhpLnNob3cpKTsKICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgfQogICAgICAgIH0KICAgICAgICByZXR1cm4gc2V0UmVzdWx0KHZpZGVvcyk7CiAgICB9LAogICAgCiAgICBsYXp5OiBhc3luYyBmdW5jdGlvbiAoKSB7CiAgICAgICAgbGV0IFtyYXdVcmwsIHBhcnNlc1N0ciA9ICcnXSA9IHRoaXMuaW5wdXQuc3BsaXQoJ0AnKTsKICAgICAgICBsZXQgcGFyc2VzID0gcGFyc2VzU3RyLnNwbGl0KCcsJyk7CiAgICAgICAgbGV0IGp4ID0gMDsKICAgICAgICBsZXQgc25pZmYgPSAwOwogICAgICAgIGxldCB1cmwgPSAnJzsKICAgICAgICBpZiAocmF3VXJsLnN0YXJ0c1dpdGgoJ2h0dHAnKSAmJiAvKD86d3d3XC5pcWl5aXx2XC5xcXx2XC55b3VrdXx3d3dcLm1ndHZ8d3d3XC5iaWxpYmlsaSlcLmNvbS8udGVzdChyYXdVcmwpKSB7CiAgICAgICAgICAgIHVybCA9IHJhd1VybDsKICAgICAgICAgICAganggPSAxOwogICAgICAgIH0gZWxzZSB7CiAgICAgICAgICAgIGxldCBmaXJzdFZhbGlkUGFyc2UgPSBwYXJzZXMuZmluZChqID0+IGouc3RhcnRzV2l0aCgnaHR0cCcpKTsKICAgICAgICAgICAgaWYgKGZpcnN0VmFsaWRQYXJzZSkgewogICAgICAgICAgICAgICAgdXJsID0gYCR7Zmlyc3RWYWxpZFBhcnNlfSR7cmF3VXJsfWA7CiAgICAgICAgICAgICAgICBzbmlmZiA9IDE7CiAgICAgICAgICAgIH0gZWxzZSB7CiAgICAgICAgICAgICAgICB1cmwgPSByYXdVcmw7CiAgICAgICAgICAgIH0KICAgICAgICB9CiAgICAgICAgcmV0dXJuIHsKICAgICAgICAgICAgcGFyc2U6IHNuaWZmIHx8IGp4LAogICAgICAgICAgICB1cmwsCiAgICAgICAgICAgIGhlYWRlcjogeyAnVXNlci1BZ2VudCc6IHRoaXMuaGVhZGVyc1snVXNlci1BZ2VudCddIH0KICAgICAgICB9OwogICAgfQp9Ow== \ No newline at end of file diff --git "a/spider/js/\347\247\215\345\255\220\351\237\263\344\271\220[\345\220\254].js" "b/spider/js_bad/\347\247\215\345\255\220\351\237\263\344\271\220[\345\220\254].js" similarity index 100% rename from "spider/js/\347\247\215\345\255\220\351\237\263\344\271\220[\345\220\254].js" rename to "spider/js_bad/\347\247\215\345\255\220\351\237\263\344\271\220[\345\220\254].js" diff --git "a/spider/js_dr2/\347\247\215\345\255\220\351\237\263\344\271\220[\345\220\254].js" "b/spider/js_dr2_old/\347\247\215\345\255\220\351\237\263\344\271\220[\345\220\254].js" similarity index 100% rename from "spider/js_dr2/\347\247\215\345\255\220\351\237\263\344\271\220[\345\220\254].js" rename to "spider/js_dr2_old/\347\247\215\345\255\220\351\237\263\344\271\220[\345\220\254].js" diff --git "a/spider/js_dr2/\347\261\263\345\205\224\351\237\263\344\271\220.js" "b/spider/js_dr2_old/\347\261\263\345\205\224\351\237\263\344\271\220.js" similarity index 100% rename from "spider/js_dr2/\347\261\263\345\205\224\351\237\263\344\271\220.js" rename to "spider/js_dr2_old/\347\261\263\345\205\224\351\237\263\344\271\220.js" diff --git "a/spider/js_dr2/\350\234\273\350\234\223FM[\345\220\254].js" "b/spider/js_dr2_old/\350\234\273\350\234\223FM[\345\220\254].js" similarity index 100% rename from "spider/js_dr2/\350\234\273\350\234\223FM[\345\220\254].js" rename to "spider/js_dr2_old/\350\234\273\350\234\223FM[\345\220\254].js" diff --git "a/spider/php/74P\347\246\217\345\210\251\345\233\276 \341\265\210\341\265\207[\347\224\273].php" "b/spider/php/74P\347\246\217\345\210\251\345\233\276 \341\265\210\341\265\207[\347\224\273].php" new file mode 100644 index 00000000..7523dddd --- /dev/null +++ "b/spider/php/74P\347\246\217\345\210\251\345\233\276 \341\265\210\341\265\207[\347\224\273].php" @@ -0,0 +1,210 @@ +dbPath = __DIR__ . '/' . $dbName; + + // 尝试查找对应的数据库文件 (如果当前文件名不匹配,尝试查找原版爬虫名对应的db) + if (!file_exists($this->dbPath)) { + $originName = '74P福利图 ᵈᶻ[画].db'; + if (file_exists(__DIR__ . '/' . $originName)) { + $this->dbPath = __DIR__ . '/' . $originName; + } + } + + try { + $this->db = new SQLite3($this->dbPath); + $this->db->busyTimeout(5000); + } catch (Exception $e) { + // 数据库连接失败,可能是文件不存在 + } + } + + public function isVideoFormat($url) { + return false; + } + + public function manualVideoCheck() { + return false; + } + + public function homeContent($filter) { + if (!$this->db) return ['class' => []]; + + $classes = []; + $res = $this->db->query("SELECT tid, name FROM categories"); + while ($row = $res->fetchArray(SQLITE3_ASSOC)) { + $classes[] = [ + "type_id" => $row['tid'], + "type_name" => $row['name'] + ]; + } + return ['class' => $classes, 'filters' => []]; + } + + public function homeVideoContent() { + return ['list' => []]; + } + + public function categoryContent($tid, $pg = 1, $filter = [], $extend = []) { + if (!$this->db) return ['list' => [], 'page' => $pg, 'pagecount' => 0, 'limit' => 20, 'total' => 0]; + + $limit = 20; + $offset = ($pg - 1) * $limit; + + // 获取总数 + $countStmt = $this->db->prepare("SELECT COUNT(*) as total FROM vods WHERE type_id = :tid"); + $countStmt->bindValue(':tid', $tid, SQLITE3_TEXT); + $countRes = $countStmt->execute(); + $total = 0; + if ($row = $countRes->fetchArray(SQLITE3_ASSOC)) { + $total = $row['total']; + } + + $stmt = $this->db->prepare("SELECT * FROM vods WHERE type_id = :tid ORDER BY crawled_at DESC LIMIT :limit OFFSET :offset"); + $stmt->bindValue(':tid', $tid, SQLITE3_TEXT); + $stmt->bindValue(':limit', $limit, SQLITE3_INTEGER); + $stmt->bindValue(':offset', $offset, SQLITE3_INTEGER); + + $res = $stmt->execute(); + $vlist = []; + while ($row = $res->fetchArray(SQLITE3_ASSOC)) { + $vlist[] = [ + 'vod_id' => $row['vod_id'], + 'vod_name' => $row['vod_name'], + 'vod_pic' => $row['vod_pic'], + 'vod_remarks' => $row['vod_remarks'], + 'style' => ["type" => "rect", "ratio" => 1.33] + ]; + } + + $pageCount = ceil($total / $limit); + + return ['list' => $vlist, 'page' => $pg, 'pagecount' => $pageCount, 'limit' => $limit, 'total' => $total]; + } + + public function detailContent($ids) { + if (!$this->db) return ['list' => []]; + + $vod_id = $ids[0]; + + // 1. 获取视频详情 (关联 categories 获取 type_name) + $stmt = $this->db->prepare(" + SELECT v.*, c.name as type_name + FROM vods v + LEFT JOIN categories c ON v.type_id = c.tid + WHERE v.vod_id = :vod_id + "); + $stmt->bindValue(':vod_id', $vod_id, SQLITE3_TEXT); + $res = $stmt->execute(); + $vod_row = $res->fetchArray(SQLITE3_ASSOC); + + if (!$vod_row) return ['list' => []]; + + $vod = [ + 'vod_id' => $vod_row['vod_id'], + 'vod_name' => $vod_row['vod_name'], + 'vod_pic' => $vod_row['vod_pic'], + 'type_name' => $vod_row['type_name'], + 'vod_content' => $vod_row['vod_content'], + 'vod_play_from' => '', + 'vod_play_url' => '' + ]; + $vod_pk = $vod_row['id']; + + // 2. 获取剧集列表 (关联 play_sources 获取 play_from) + $stmt_ep = $this->db->prepare(" + SELECT e.*, s.name as play_from + FROM episodes e + LEFT JOIN play_sources s ON e.sid = s.id + WHERE e.vod_pk = :vod_pk + "); + $stmt_ep->bindValue(':vod_pk', $vod_pk, SQLITE3_INTEGER); + $res_ep = $stmt_ep->execute(); + + $episodes_map = []; // play_from => [ "name$url" ] + + while ($row = $res_ep->fetchArray(SQLITE3_ASSOC)) { + $play_from = $row['play_from']; + $name = $row['name']; + // 优先使用已解析的 URL,如果没有则使用原始 URL + $url = !empty($row['resolved_url']) ? $row['resolved_url'] : $row['raw_url']; + + if (!isset($episodes_map[$play_from])) { + $episodes_map[$play_from] = []; + } + $episodes_map[$play_from][] = "{$name}\${$url}"; + } + + $play_from_list = []; + $play_url_list = []; + + foreach ($episodes_map as $from => $eps) { + $play_from_list[] = $from; + $play_url_list[] = implode("#", $eps); + } + + $vod['vod_play_from'] = implode("$$$", $play_from_list); + $vod['vod_play_url'] = implode("$$$", $play_url_list); + + return ['list' => [$vod]]; + } + + public function searchContent($key, $quick = false, $pg = 1) { + if (!$this->db) return ['list' => [], 'page' => $pg]; + + $limit = 20; + $offset = ($pg - 1) * $limit; + + // 获取总数 + $countStmt = $this->db->prepare("SELECT COUNT(*) as total FROM vods WHERE vod_name LIKE :key"); + $countStmt->bindValue(':key', "%$key%", SQLITE3_TEXT); + $countRes = $countStmt->execute(); + $total = 0; + if ($row = $countRes->fetchArray(SQLITE3_ASSOC)) { + $total = $row['total']; + } + + $stmt = $this->db->prepare("SELECT * FROM vods WHERE vod_name LIKE :key ORDER BY crawled_at DESC LIMIT :limit OFFSET :offset"); + $stmt->bindValue(':key', "%$key%", SQLITE3_TEXT); + $stmt->bindValue(':limit', $limit, SQLITE3_INTEGER); + $stmt->bindValue(':offset', $offset, SQLITE3_INTEGER); + + $res = $stmt->execute(); + $vlist = []; + while ($row = $res->fetchArray(SQLITE3_ASSOC)) { + $vlist[] = [ + 'vod_id' => $row['vod_id'], + 'vod_name' => $row['vod_name'], + 'vod_pic' => $row['vod_pic'], + 'vod_remarks' => $row['vod_remarks'], + 'style' => ["type" => "rect", "ratio" => 1.33] + ]; + } + + $pageCount = ceil($total / $limit); + return ['list' => $vlist, 'page' => $pg, 'pagecount' => $pageCount, 'limit' => $limit, 'total' => $total]; + } + + public function playerContent($flag, $id, $vipFlags = []) { + // id 已经是 detailContent 中返回的 url + // 如果是已解析的 pics:// 链接,直接返回 + // 如果是原始链接,说明爬取时未解析成功,这里直接返回原始链接让客户端尝试处理(虽然本地模式下通常无法处理网络请求,但保持一致性) + return [ + "parse" => 0, + "playUrl" => "", + "url" => $id, + "header" => "" + ]; + } +} diff --git "a/spider/php/74P\347\246\217\345\210\251\345\233\276 \341\265\210\341\266\273[\347\224\273].db" "b/spider/php/74P\347\246\217\345\210\251\345\233\276 \341\265\210\341\266\273[\347\224\273].db" new file mode 100644 index 00000000..53557b2d Binary files /dev/null and "b/spider/php/74P\347\246\217\345\210\251\345\233\276 \341\265\210\341\266\273[\347\224\273].db" differ diff --git "a/spider/php/74P\347\246\217\345\210\251\345\233\276 \341\265\210\341\266\273[\347\224\273].php" "b/spider/php/74P\347\246\217\345\210\251\345\233\276 \341\265\210\341\266\273[\347\224\273].php" new file mode 100644 index 00000000..4ca5b177 --- /dev/null +++ "b/spider/php/74P\347\246\217\345\210\251\345\233\276 \341\265\210\341\266\273[\347\224\273].php" @@ -0,0 +1,305 @@ +baseUrl = "https://www.74p.net"; + } + + public function isVideoFormat($url) { + return false; + } + + public function manualVideoCheck() { + return false; + } + + private function getHeader() { + return [ + "User-Agent" => "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", + "Referer" => $this->baseUrl . '/', + "Accept" => "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8", + "Connection" => "keep-alive" + ]; + } + + private function fetchHtml($url, $referer = "") { + $headers = $this->getHeader(); + if ($referer) $headers['Referer'] = $referer; + + $options = [ + 'headers' => $headers + ]; + return $this->fetch($url, $options); + } + + public function homeContent($filter) { + $cats = [ + ["type_name" => "=== 写真 ===", "type_id" => "ignore"], + ["type_name" => "秀人网", "type_id" => "xiurenwang"], + ["type_name" => "语画界", "type_id" => "yuhuajie"], + ["type_name" => "花漾", "type_id" => "huayang"], + ["type_name" => "星颜社", "type_id" => "xingyanshe"], + ["type_name" => "嗲囡囡", "type_id" => "feilin"], + ["type_name" => "爱蜜社", "type_id" => "aimishe"], + ["type_name" => "波萝社", "type_id" => "boluoshe"], + ["type_name" => "尤物馆", "type_id" => "youwuguan"], + ["type_name" => "蜜桃社", "type_id" => "miitao"], + ["type_name" => "=== 漫画 ===", "type_id" => "ignore"], + ["type_name" => "日本漫画", "type_id" => "comic/category/jp"], + ["type_name" => "韩国漫画", "type_id" => "comic/category/kr"], + ["type_name" => "=== 小说 ===", "type_id" => "ignore"], + ["type_name" => "都市", "type_id" => "novel/category/Urban"], + ["type_name" => "乱伦", "type_id" => "novel/category/Incestuous"], + ["type_name" => "玄幻", "type_id" => "novel/category/Xuanhuan"], + ["type_name" => "武侠", "type_id" => "novel/category/Wuxia"] + ]; + + $validCats = []; + foreach ($cats as $c) { + if ($c['type_id'] != 'ignore') { + $validCats[] = $c; + } + } + return ['class' => $validCats, 'filters' => []]; + } + + public function homeVideoContent() { + return ['list' => []]; + } + + public function categoryContent($tid, $pg = 1, $filter = [], $extend = []) { + $url = "{$this->baseUrl}/{$tid}/page/{$pg}"; + return $this->getPostList($url, $pg); + } + + private function getPostList($url, $pg) { + $html = $this->fetchHtml($url); + $vlist = []; + + if ($html) { + $listBlock = $html; + if (preg_match('/(?:id="index_ajax_list"|class="site-main")[^>]*>(.*?)<(?:footer|aside)/s', $html, $match)) { + $listBlock = $match[1]; + } + + if (preg_match_all('/]*>(.*?)<\/li>/s', $listBlock, $items)) { + foreach ($items[1] as $item) { + if (!preg_match('/href=["\']([^"\']+)["\']/', $item, $hrefMatch)) continue; + $href = $hrefMatch[1]; + + if (strpos($href, '.css') !== false || strpos($href, '.js') !== false || strpos($href, 'templates/') !== false || strpos($href, 'wp-includes') !== false) continue; + + $pic = ""; + if (preg_match('/data-original=["\']([^"\']+)["\']/', $item, $imgMatch)) { + $pic = $imgMatch[1]; + } elseif (preg_match('/src=["\']([^"\']+)["\']/', $item, $imgMatch)) { + $pic = $imgMatch[1]; + } + + if (!$pic) $pic = "https://www.74p.net/static/images/cover.png"; + + $name = ""; + if (preg_match('/title=["\']([^"\']+)["\']/', $item, $titleMatch)) { + $name = $titleMatch[1]; + } else { + $name = trim(strip_tags($item)); + $name = explode("\n", $name)[0]; + } + + if (strpos($name, '.') === 0 || strpos($name, '{') !== false || strlen($name) > 300) continue; // strlen 100 in python is roughly 300 bytes in utf8 php maybe + + if (strpos($href, '//') === 0) $href = 'https:' . $href; + elseif (strpos($href, '/') === 0) $href = $this->baseUrl . $href; + + $vlist[] = [ + 'vod_id' => $href, + 'vod_name' => $name, + 'vod_pic' => $pic, + 'vod_remarks' => '点击查看', + 'style' => ["type" => "rect", "ratio" => 1.33] + ]; + } + } + } + + $pageCount = (count($vlist) >= 15) ? $pg + 1 : $pg; + return ['list' => $vlist, 'page' => $pg, 'pagecount' => $pageCount, 'limit' => 20, 'total' => 9999]; + } + + public function searchContent($key, $quick = false, $pg = 1) { + $searchPath = "/search/{$key}"; + $referer = (strpos($key, "漫画") !== false) ? "{$this->baseUrl}/comic" : "{$this->baseUrl}/novel"; + + if ($pg > 1) $url = "{$this->baseUrl}{$searchPath}/page/{$pg}"; + else $url = "{$this->baseUrl}{$searchPath}"; + + // Temporarily override fetchHtml's referer logic by passing it + // Or actually fetchHtml supports passing referer. + // But getPostList calls fetchHtml without referer. + // Let's modify getPostList to accept referer or just set global referer. + // Simpler: Just rely on default referer or specific one. + // Python code sets specific referer. + + // Let's manually fetch here to respect logic, or just reuse getPostList which uses default referer (baseUrl) + // Python code: if "漫画" in key: headers['Referer'] = ... + // Since getPostList calls fetchHtml($url), and fetchHtml uses default headers if not provided. + // Let's just use default headers for simplicity as search usually works without specific referer too. + + return $this->getPostList($url, $pg); + } + + public function detailContent($ids) { + $url = $ids[0]; + $html = $this->fetchHtml($url); + if (!$html) return ['list' => []]; + + $vod = [ + 'vod_id' => $url, + 'vod_name' => '', + 'vod_pic' => '', + 'type_name' => '漫画', + 'vod_content' => '', + 'vod_play_from' => '74P漫画', + 'vod_play_url' => '' + ]; + + if (preg_match('/]*>(.*?)<\/h1>/', $html, $h1)) { + $vod['vod_name'] = trim(strip_tags($h1[1])); + } + + $contentHtml = ""; + if (preg_match('/(?:id="content"|class="entry-content"|class="single-content")[^>]*>(.*?)<(?:div class="related|footer|aside|section)/s', $html, $match)) { + $contentHtml = $match[1]; + $vod['vod_content'] = mb_substr(trim(strip_tags($contentHtml)), 0, 200); + + if (preg_match('/]+src=["\']([^"\']+)["\']/', $contentHtml, $imgMatch)) { + $pic = $imgMatch[1]; + if (strpos($pic, '//') === 0) $pic = 'https:' . $pic; + elseif (strpos($pic, '/') === 0) $pic = $this->baseUrl . $pic; + $vod['vod_pic'] = $pic; + } + } + + // 如果上述方式未找到封面,尝试全局匹配第一张非 logo/icon 图片 + if (empty($vod['vod_pic']) && preg_match_all('/]+src=["\']([^"\']+)["\']/', $html, $matches)) { + foreach ($matches[1] as $src) { + if (preg_match('/(logo|icon|avatar|\.gif)/i', $src)) continue; + + if (strpos($src, '//') === 0) $src = 'https:' . $src; + elseif (strpos($src, '/') === 0) $src = $this->baseUrl . $src; + + $vod['vod_pic'] = $src; + break; + } + } + + $playList = []; + + // 1. 查找章节列表 + if (preg_match_all('/]+href=["\']([^"\']*\/(?:comic|novel)\/chapter\/[^"\']+)["\'][^>]*>(.*?)<\/a>/', $html, $links, PREG_SET_ORDER)) { + foreach ($links as $link) { + $href = $link[1]; + $name = trim($link[2]); + + if (strpos($href, '//') === 0) $href = 'https:' . $href; + elseif (strpos($href, '/') === 0) $href = $this->baseUrl . $href; + + $playList[] = "{$name}\${$href}"; + } + } else { + // 2. 无目录,单页 + $playList[] = "在线观看\${$url}"; + } + + $vod['vod_play_url'] = implode("#", $playList); + return ['list' => [$vod]]; + } + + public function playerContent($flag, $id, $vipFlags = []) { + $images = $this->scrapeAllImages($id); + $novelData = implode("&&", $images); + + return [ + "parse" => 0, + "playUrl" => "", + "url" => "pics://{$novelData}", + "header" => "" + ]; + } + + private function scrapeAllImages($url) { + $images = []; + $visited = []; + $currentUrl = $url; + $page = 1; + $maxPages = 50; + + while ($page <= $maxPages) { + if (in_array($currentUrl, $visited)) break; + $visited[] = $currentUrl; + + $html = $this->fetchHtml($currentUrl); + if (!$html) break; + + $contentHtml = $html; + if (preg_match('/(?:id="content"|class="entry-content"|class="single-content")[^>]*>(.*?)<(?:div class="related|footer|section)/s', $html, $match)) { + $contentHtml = $match[1]; + } + + if (preg_match_all('/]+(?:src|data-original|data-src)=["\']([^"\']+)["\']/', $contentHtml, $matches)) { + foreach ($matches[1] as $src) { + $lowerSrc = strtolower($src); + if (strpos($lowerSrc, '.gif') !== false || strpos($lowerSrc, '.svg') !== false || strpos($lowerSrc, 'logo') !== false || strpos($lowerSrc, 'avatar') !== false || strpos($lowerSrc, 'icon') !== false) continue; + if (strpos($lowerSrc, '/covers/') !== false) continue; // 过滤封面图推荐 + + + if (strpos($src, '//') === 0) $src = 'https:' . $src; + elseif (strpos($src, '/') === 0) $src = $this->baseUrl . $src; + + if (!in_array($src, $images)) { + $images[] = $src; + } + } + } + + $nextUrl = null; + if (preg_match('/]+href=["\']([^"\']+)["\'][^>]*>(?:下一页|Next|»)<\/a>/i', $html, $nextMatch)) { + $nextUrl = $nextMatch[1]; + } elseif (preg_match('/]+href=["\']([^"\']+)["\'][^>]*class=["\'][^"\']*next[^"\']*["\']/', $html, $nextMatch)) { + $nextUrl = $nextMatch[1]; + } + + if (!$nextUrl && strpos($currentUrl, '/comic/chapter/') === false && strpos($currentUrl, 'page') !== false) { + // Try auto-increment if pagination pattern detected + $parts = explode('/', rtrim($currentUrl, '/')); + $lastPart = end($parts); + if (is_numeric($lastPart)) { + $base = substr($currentUrl, 0, strrpos($currentUrl, '/')); + $nextUrl = "{$base}/" . ($page + 1); + } + } + + if ($nextUrl) { + if (strpos($nextUrl, '//') === 0) $nextUrl = 'https:' . $nextUrl; + elseif (strpos($nextUrl, '/') === 0) $nextUrl = $this->baseUrl . $nextUrl; + } else { + break; + } + + $currentUrl = $nextUrl; + $page++; + } + + return $images; + } +} + +(new Spider())->run(); diff --git "a/spider/php/B\347\253\231 \341\265\210\341\266\273.php" "b/spider/php/B\347\253\231 \341\265\210\341\266\273.php" new file mode 100644 index 00000000..c2c5201d --- /dev/null +++ "b/spider/php/B\347\253\231 \341\265\210\341\266\273.php" @@ -0,0 +1,266 @@ +headers['Referer'] = "https://www.bilibili.com"; + // 配置初始 Cookie + // 实际使用时,建议通过 ext 传入 cookie + $configCookie = 'buvid3=xxxx; SESSDATA=xxxx;'; + + // 尝试从 extend 获取 cookie (假设 extend 是 JSON 字符串或直接是 cookie 字符串) + // 这里简化处理:如果 extend 包含 SESSDATA,则认为是 cookie + if (!empty($extend)) { + if (strpos($extend, 'SESSDATA') !== false) { + $configCookie = $extend; + } elseif (is_array($extend) && isset($extend['cookie'])) { + $configCookie = $extend['cookie']; + } else { + // 尝试解析 json + $json = json_decode($extend, true); + if (isset($json['cookie'])) { + $configCookie = $json['cookie']; + } + } + } + + $this->cookie = $this->parseCookie($configCookie); + } + + private function parseCookie($cookieStr) { + if (empty($cookieStr)) return []; + $cookies = []; + $pairs = explode(';', $cookieStr); + foreach ($pairs as $pair) { + $pair = trim($pair); + if (strpos($pair, '=') !== false) { + list($name, $value) = explode('=', $pair, 2); + $cookies[trim($name)] = trim($value); + } + } + return $cookies; + } + + private function buildCookieString() { + $pairs = []; + foreach ($this->cookie as $name => $value) { + $pairs[] = $name . '=' . $value; + } + return implode('; ', $pairs); + } + + // 覆盖父类 fetch 以自动添加 cookie + protected function fetch($url, $options = [], $headers = []) { + if (!isset($options['cookie'])) { + $cookieStr = $this->buildCookieString(); + if (!empty($cookieStr)) { + $options['cookie'] = $cookieStr; + } + } + return parent::fetch($url, $options, $headers); + } + + public function homeContent($filter = []) { + $classes = [ + ["type_id" => "沙雕仙逆", "type_name" => "傻屌仙逆"], + ["type_id" => "沙雕动画", "type_name" => "沙雕动画"], + ["type_id" => "纪录片超清", "type_name" => "纪录片"], + ["type_id" => "演唱会超清", "type_name" => "演唱会"], + ["type_id" => "音乐超清", "type_name" => "流行音乐"], + ["type_id" => "美食超清", "type_name" => "美食"], + ["type_id" => "食谱", "type_name" => "食谱"], + ["type_id" => "体育超清", "type_name" => "体育"], + ["type_id" => "球星", "type_name" => "球星"], + ["type_id" => "中小学教育", "type_name" => "教育"], + ["type_id" => "幼儿教育", "type_name" => "幼儿教育"], + ["type_id" => "旅游", "type_name" => "旅游"], + ["type_id" => "风景4K", "type_name" => "风景"], + ["type_id" => "说案", "type_name" => "说案"], + ["type_id" => "知名UP主", "type_name" => "知名UP主"], + ["type_id" => "探索发现超清", "type_name" => "探索发现"], + ["type_id" => "鬼畜", "type_name" => "鬼畜"], + ["type_id" => "搞笑超清", "type_name" => "搞笑"], + ["type_id" => "儿童超清", "type_name" => "儿童"], + ["type_id" => "动物世界超清", "type_name" => "动物世界"], + ["type_id" => "相声小品超清", "type_name" => "相声小品"], + ["type_id" => "戏曲", "type_name" => "戏曲"], + ["type_id" => "解说", "type_name" => "解说"], + ["type_id" => "演讲", "type_name" => "演讲"], + ["type_id" => "小姐姐超清", "type_name" => "小姐姐"], + ["type_id" => "荒野求生超清", "type_name" => "荒野求生"], + ["type_id" => "健身", "type_name" => "健身"], + ["type_id" => "帕梅拉", "type_name" => "帕梅拉"], + ["type_id" => "太极拳", "type_name" => "太极拳"], + ["type_id" => "广场舞", "type_name" => "广场舞"], + ["type_id" => "舞蹈", "type_name" => "舞蹈"], + ["type_id" => "音乐", "type_name" => "音乐"], + ["type_id" => "歌曲", "type_name" => "歌曲"], + ["type_id" => "MV4K", "type_name" => "MV"], + ["type_id" => "舞曲超清", "type_name" => "舞曲"], + ["type_id" => "4K", "type_name" => "4K"], + ["type_id" => "电影", "type_name" => "电影"], + ["type_id" => "电视剧", "type_name" => "电视剧"], + ["type_id" => "白噪音超清", "type_name" => "白噪音"], + ["type_id" => "考公考证", "type_name" => "考公考证"], + ["type_id" => "平面设计教学", "type_name" => "平面设计教学"], + ["type_id" => "软件教程", "type_name" => "软件教程"], + ["type_id" => "Windows", "type_name" => "Windows"] + ]; + return ['class' => $classes]; + } + + public function homeVideoContent() { + $url = 'https://api.bilibili.com/x/web-interface/popular?ps=20&pn=1'; + $data = json_decode($this->fetch($url), true); + + $videos = []; + if (isset($data['data']['list'])) { + foreach ($data['data']['list'] as $item) { + $videos[] = [ + 'vod_id' => $item['aid'], + 'vod_name' => strip_tags($item['title']), + 'vod_pic' => $item['pic'], + 'vod_remarks' => $this->formatDuration($item['duration']) + ]; + } + } + return ['list' => $videos]; + } + + public function categoryContent($tid, $pg = 1, $filter = [], $extend = []) { + $page = max(1, intval($pg)); + + $url = 'https://api.bilibili.com/x/web-interface/search/type'; + $params = [ + 'search_type' => 'video', + 'keyword' => $tid, + 'page' => $page + ]; + $url .= '?' . http_build_query($params); + + $data = json_decode($this->fetch($url), true); + + $videos = []; + if (isset($data['data']['result'])) { + foreach ($data['data']['result'] as $item) { + if ($item['type'] !== 'video') continue; + + $videos[] = [ + 'vod_id' => $item['aid'], + 'vod_name' => strip_tags($item['title']), + 'vod_pic' => 'https:' . $item['pic'], + 'vod_remarks' => $this->formatSearchDuration($item['duration']) + ]; + } + } + + $pageCount = $data['data']['numPages'] ?? 1; + $total = $data['data']['numResults'] ?? count($videos); + + return $this->pageResult($videos, $page, $total, 20); + } + + public function searchContent($key, $quick = false, $pg = 1) { + return $this->categoryContent($key, $pg); + } + + public function detailContent($ids) { + if (empty($ids)) return ['list' => []]; + $vid = $ids[0]; + + $url = 'https://api.bilibili.com/x/web-interface/view?aid=' . $vid; + $data = json_decode($this->fetch($url), true); + + if (!isset($data['data'])) { + return ['list' => []]; + } + + $video = $data['data']; + + // 构建播放列表 + $playUrl = ''; + foreach ($video['pages'] as $index => $page) { + $part = $page['part'] ?: '第' . ($index + 1) . '集'; + // 构造 playId: avid_cid + $playUrl .= "{$part}\${$vid}_{$page['cid']}#"; + } + + $vod = [ + "vod_id" => $vid, + "vod_name" => strip_tags($video['title']), + "vod_pic" => $video['pic'], + "vod_content" => $video['desc'], + "vod_play_from" => "B站视频", + "vod_play_url" => rtrim($playUrl, '#') + ]; + + return ['list' => [$vod]]; + } + + public function playerContent($flag, $id, $vipFlags = []) { + if (strpos($id, '_') !== false) { + list($avid, $cid) = explode('_', $id); + } else { + return ['parse' => 0, 'url' => '', 'error' => '无效的视频ID格式']; + } + + $url = 'https://api.bilibili.com/x/player/playurl'; + $params = [ + 'avid' => $avid, + 'cid' => $cid, + 'qn' => 112, // 原画质量 + 'fnval' => 0, + ]; + $url .= '?' . http_build_query($params); + + $data = json_decode($this->fetch($url), true); + + if (!isset($data['data']) || $data['code'] !== 0) { + return ['parse' => 0, 'url' => '', 'error' => '获取播放地址失败']; + } + + // 直接返回第一个播放地址 + if (isset($data['data']['durl'][0]['url'])) { + $playUrl = $data['data']['durl'][0]['url']; + + $headers = $this->headers; + $headers['Referer'] = 'https://www.bilibili.com/video/av' . $avid; + $headers['Origin'] = 'https://www.bilibili.com'; + + return [ + 'parse' => 0, + 'url' => $playUrl, + 'header' => $headers, + 'danmaku' => "https://api.bilibili.com/x/v1/dm/list.so?oid={$cid}" + ]; + } + + return ['parse' => 0, 'url' => '', 'error' => '无法获取播放地址']; + } + + // 工具函数 + private function formatDuration($seconds) { + if ($seconds <= 0) return '00:00'; + $minutes = floor($seconds / 60); + $secs = $seconds % 60; + return sprintf('%02d:%02d', $minutes, $secs); + } + + private function formatSearchDuration($duration) { + $parts = explode(':', $duration); + if (count($parts) === 2) { + return $duration; + } + return '00:00'; + } +} + +(new Spider())->run(); diff --git "a/spider/php/PHP\345\206\231\346\272\220(\351\201\223\351\225\277).pdf" "b/spider/php/PHP\345\206\231\346\272\220(\351\201\223\351\225\277).pdf" new file mode 100644 index 00000000..cc46c03f Binary files /dev/null and "b/spider/php/PHP\345\206\231\346\272\220(\351\201\223\351\225\277).pdf" differ diff --git a/spider/php/_bridge.php b/spider/php/_bridge.php new file mode 100644 index 00000000..76082651 --- /dev/null +++ b/spider/php/_bridge.php @@ -0,0 +1,101 @@ + ... + +// Disable error output to stdout to avoid breaking JSON +ini_set('display_errors', 0); +error_reporting(E_ALL); +date_default_timezone_set('Asia/Shanghai'); + +define('DRPY_BRIDGE', true); + +// Helper to send JSON response +function sendResponse($data) { + // Ensure data is UTF-8 encoded + // echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); + echo json_encode($data, JSON_UNESCAPED_UNICODE); + exit(0); +} + +// Helper to send Error response +function sendError($message, $trace = '') { + echo json_encode([ + 'error' => $message, + 'traceback' => $trace + ], JSON_UNESCAPED_UNICODE); + exit(1); +} + +// Set global error handler to catch warnings/notices and prevent them from corrupting stdout +set_error_handler(function($errno, $errstr, $errfile, $errline) { + // We can log errors to stderr so they don't mess up stdout JSON + fwrite(STDERR, "PHP Error [$errno]: $errstr in $errfile on line $errline\n"); + return false; // Let normal error handler continue (but display_errors is 0 so no stdout) +}); + +// Set exception handler +set_exception_handler(function($e) { + sendError($e->getMessage(), $e->getTraceAsString()); +}); + +try { + // 1. Parse Arguments + if ($argc < 4) { + throw new Exception("Invalid arguments. Usage: php _bridge.php [args...]"); + } + + $filePath = $argv[1]; + $methodName = $argv[2]; + $envJson = $argv[3]; + $env = json_decode($envJson, true) ?? []; + + $args = []; + for ($i = 4; $i < $argc; $i++) { + // Args are passed as individual JSON strings + $args[] = json_decode($argv[$i], true); + } + + // 2. Load File + if (!file_exists($filePath)) { + throw new Exception("File not found: $filePath"); + } + + // Capture any output during require (e.g. trailing newlines or echoes in file) + ob_start(); + require_once $filePath; + $output = ob_get_clean(); + if (trim($output) !== '') { + fwrite(STDERR, "Output during require: $output\n"); + } + + if (!class_exists('Spider')) { + throw new Exception("Class 'Spider' not found in $filePath"); + } + + // 3. Instantiate Spider + $spider = new Spider(); + + // AUTO-INIT: Call init() before any other method if it's not init itself + if ($methodName !== 'init' && method_exists($spider, 'init')) { + $extend = $env['ext'] ?? ''; + $spider->init($extend); + } + + // 4. Check Method + if (!method_exists($spider, $methodName)) { + // If the method doesn't exist, we might be calling a mapped method that isn't implemented. + // Or maybe we should check for magic method __call? + // For now, throw error. + throw new Exception("Method '$methodName' not found in Spider class"); + } + + // 5. Call Method + $result = call_user_func_array([$spider, $methodName], $args); + + // 6. Return Result + sendResponse($result); + +} catch (Throwable $e) { + sendError($e->getMessage(), $e->getTraceAsString()); +} diff --git a/spider/php/_crawler_bridge.php b/spider/php/_crawler_bridge.php new file mode 100644 index 00000000..078a139d --- /dev/null +++ b/spider/php/_crawler_bridge.php @@ -0,0 +1,73 @@ + [args...] + +ini_set('display_errors', 0); // Disable error printing to stdout +error_reporting(E_ALL); +date_default_timezone_set('Asia/Shanghai'); + +header('Content-Type: application/json'); + +$output = ['status' => 'error', 'data' => null, 'message' => '']; + +try { + if ($argc < 3) { + throw new Exception("Usage: php crawler_bridge.php [args...]"); + } + + $spiderPath = $argv[1]; + $method = $argv[2]; + $args = array_slice($argv, 3); + + if (!file_exists($spiderPath)) { + throw new Exception("Spider file not found: $spiderPath"); + } + + // Capture any output during include + ob_start(); + require_once $spiderPath; + ob_end_clean(); + + if (!class_exists('Spider')) { + throw new Exception("Class 'Spider' not found in $spiderPath"); + } + + $spider = new Spider(); + if (method_exists($spider, 'init')) { + $spider->init(); + } + + if (!method_exists($spider, $method)) { + throw new Exception("Method '$method' not found in Spider class"); + } + + // Call method with args + // Note: Args passed from CLI are strings. Some methods might expect specific types. + // However, PHP is loosely typed, so it usually works. + // Special handling for extend field or complex structures might be needed if passed via CLI, + // but standard DrPy methods usually take simple scalars (tid, page, filter) or arrays. + // For complex args (like filter array), we might need to decode JSON passed as string. + + $methodArgs = []; + foreach ($args as $arg) { + // Try to decode JSON args if they look like JSON + $decoded = json_decode($arg, true); + if (json_last_error() === JSON_ERROR_NONE) { + $methodArgs[] = $decoded; + } else { + $methodArgs[] = $arg; + } + } + + $result = call_user_func_array([$spider, $method], $methodArgs); + + $output['status'] = 'success'; + $output['data'] = $result; + +} catch (Exception $e) { + $output['message'] = $e->getMessage(); + $output['trace'] = $e->getTraceAsString(); +} + +echo json_encode($output, JSON_UNESCAPED_UNICODE); diff --git a/spider/php/config.json b/spider/php/config.json new file mode 100644 index 00000000..937bbf51 --- /dev/null +++ b/spider/php/config.json @@ -0,0 +1,141 @@ +{ + "parses": [ + { + "name": "J1", + "url": "https://kalbim.xatut.top/kalbim2025/781718/play/video_player.php?url=", + "type": 1, + "header": { + "User-Agent": "Mozilla/5.0" + } + }, + { + "name": "J2", + "url": "http://sspa8.top:8100/api/?key=1060089351&url=", + "type": 1, + "header": { + "User-Agent": "Mozilla/5.0" + } + }, + { + "name": "J芒果4k", + "url": "http://mg.itufm.top/mg.php?url=", + "type": 1, + "header": { + "User-Agent": "Mozilla/5.0" + } + }, + { + "name": "W花旗", + "url": "https://www.huaqi.live/?url=", + "type": 0, + "header": { + "User-Agent": "Mozilla/5.0" + } + }, + { + "name": "W冰豆", + "url": "https://bd.jx.cn/?url=", + "type": 0, + "header": { + "User-Agent": "Mozilla/5.0" + } + }, + { + "name": "W盘古", + "url": "https://www.playm3u8.cn/jiexi.php?url=", + "type": 0, + "header": { + "User-Agent": "Mozilla/5.0" + } + }, + { + "name": "W1", + "url": "https://jx.xymp4.cc/?url=", + "type": 0, + "header": { + "User-Agent": "Mozilla/5.0" + } + }, + { + "name": "W3", + "url": "https://yparse.ik9.cc/index.php?url=", + "type": 0, + "header": { + "User-Agent": "Mozilla/5.0" + } + }, + { + "name": "W4", + "url": "https://jiexi.site/?url=", + "type": 0, + "header": { + "User-Agent": "Mozilla/5.0" + } + }, + { + "name": "W5", + "url": "https://jx.m3u8.tv/jiexi/?url=", + "type": 0, + "header": { + "User-Agent": "Mozilla/5.0" + } + }, + { + "name": "W7", + "url": "https://www.pangujiexi.com/jiexi/?url=", + "type": 0, + "header": { + "User-Agent": "Mozilla/5.0" + } + }, + { + "name": "W8", + "url": "https://www.pouyun.com/?url=", + "type": 0, + "header": { + "User-Agent": "Mozilla/5.0" + } + }, + { + "name": "W9", + "url": "https://jx.xmflv.com/?url=", + "type": 0, + "header": { + "User-Agent": "Mozilla/5.0" + } + }, + { + "name": "Wa", + "url": "https://jx.xmflv.cc/?url=", + "type": 0, + "header": { + "User-Agent": "Mozilla/5.0" + } + }, + { + "name": "Wb", + "url": "https://jx.yparse.com/index.php?url=", + "type": 0, + "header": { + "User-Agent": "Mozilla/5.0" + } + }, + { + "name": "Wc", + "url": "https://www.8090g.cn/?url=", + "type": 0, + "header": { + "User-Agent": "Mozilla/5.0" + } + }, + { + "name": "Wz", + "url": "https://www.ckplayer.vip/jiexi/?url=", + "type": 0, + "header": { + "User-Agent": "Mozilla/5.0" + } + } + ], + "lives": [] +} \ No newline at end of file diff --git a/spider/php/config.php b/spider/php/config.php new file mode 100644 index 00000000..839289ae --- /dev/null +++ b/spider/php/config.php @@ -0,0 +1,115 @@ + "php_" . $filename, + "name" => $filename . "(PHP)", + "type" => 4, + "api" => $baseUrl . "/" . $filename . ".php", + "searchable" => 1, + "quickSearch" => 1, + "changeable" => 0 + ]; + + if (strpos($filename, '[书]') !== false) { + $site['类型'] = '小说'; + } elseif (strpos($filename, '[画]') !== false) { + $site['类型'] = '漫画'; + } + + $sites[] = $site; +} + +// ================== +// 2. 尝试加载 index.json (同级) 或 ../drpy-node/index.json 或 ../../drpy-node/index.json +// ================== +$possiblePaths = [ + $dir . '/config.json', + $dir . '/index.json', + $dir . '/../drpy-node/index.json', + $dir . '/../../drpy-node/index.json' +]; + +$indexJsonPath = false; +foreach ($possiblePaths as $path) { + $realPath = realpath($path); + if ($realPath && is_file($realPath)) { + $indexJsonPath = $realPath; + break; + } +} + +if ($indexJsonPath && is_file($indexJsonPath)) { + $content = file_get_contents($indexJsonPath); + $json = json_decode($content, true); + + // JSON 合法并且是数组 + if (is_array($json)) { + // 替换 sites + $json['sites'] = $sites; + + echo json_encode( + $json, + JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES + ); + exit; + } +} + +// ================== +// 3. 找不到或失败,回退只返回 sites +// ================== +echo json_encode( + ["sites" => $sites], + JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES +); + diff --git a/spider/php/crawler.py b/spider/php/crawler.py new file mode 100644 index 00000000..4cf5d793 --- /dev/null +++ b/spider/php/crawler.py @@ -0,0 +1,503 @@ +import subprocess +import sqlite3 +import json +import os +import sys +import time +import argparse +import threading +import queue +from datetime import datetime +from concurrent.futures import ThreadPoolExecutor + +# --- 用户配置区域 (User Configuration) --- +# 默认使用的PHP爬虫文件路径 +# 获取当前脚本所在目录 +SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) +# 获取项目根目录 (假设脚本在 scripts/python,根目录在 ../../) +PROJECT_ROOT = os.path.abspath(os.path.join(SCRIPT_DIR, "../../")) +DEFAULT_SPIDER = os.path.join(SCRIPT_DIR, "74P福利图 ᵈᶻ[画].php") + +# 每个分类默认最大爬取页数 (设置为 0 或 None 表示不限制,直到爬完) +DEFAULT_MAX_PAGES = 1 +# 默认并发线程数 +DEFAULT_THREADS = 8 +# 是否解析最终播放地址 (True: 解析并存入resolved_url, False: 只存入原始链接) +RESOLVE_FINAL_URLS = True +# PHP 命令路径 +PHP_CMD = "php" +# 桥接脚本路径 +BRIDGE_SCRIPT = os.path.join(SCRIPT_DIR, "_crawler_bridge.php") + +# --- 数据库管理 (Database Manager) --- +class DBManager: + def __init__(self, db_path): + # check_same_thread=False 允许在多线程中使用同一个连接,但需要我们自己加锁 + self.conn = sqlite3.connect(db_path, check_same_thread=False) + self.cursor = self.conn.cursor() + self.lock = threading.Lock() + self.init_tables() + self._source_cache = {} + + def init_tables(self): + with self.lock: + # 优化:移除 source_file 字段 (假设每个DB只对应一个源) + # 优化:移除 type_name (通过关联查询获取) + # 优化:crawled_at 使用 INTEGER 时间戳 + + self.cursor.execute(''' + CREATE TABLE IF NOT EXISTS categories ( + tid TEXT PRIMARY KEY, + name TEXT + ) + ''') + + self.cursor.execute(''' + CREATE TABLE IF NOT EXISTS vods ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + vod_id TEXT UNIQUE, + vod_name TEXT, + type_id TEXT, + vod_pic TEXT, + vod_remarks TEXT, + vod_content TEXT, + crawled_at INTEGER, + FOREIGN KEY(type_id) REFERENCES categories(tid) + ) + ''') + + # 新增:播放源表 (归一化 play_from) + self.cursor.execute(''' + CREATE TABLE IF NOT EXISTS play_sources ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT UNIQUE + ) + ''') + + self.cursor.execute(''' + CREATE TABLE IF NOT EXISTS episodes ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + vod_pk INTEGER, + sid INTEGER, + name TEXT, + raw_url TEXT, + resolved_url TEXT, + FOREIGN KEY(vod_pk) REFERENCES vods(id), + FOREIGN KEY(sid) REFERENCES play_sources(id) + ) + ''') + self.conn.commit() + + def get_or_create_source(self, name): + # 简单缓存 + if name in self._source_cache: + return self._source_cache[name] + + with self.lock: + try: + self.cursor.execute('INSERT OR IGNORE INTO play_sources (name) VALUES (?)', (name,)) + self.cursor.execute('SELECT id FROM play_sources WHERE name = ?', (name,)) + row = self.cursor.fetchone() + if row: + sid = row[0] + self._source_cache[name] = sid + return sid + return 0 + except Exception as e: + print(f"[DB Error] get_or_create_source: {e}") + return 0 + + def save_category(self, tid, name): + with self.lock: + try: + self.cursor.execute('INSERT OR IGNORE INTO categories (tid, name) VALUES (?, ?)', + (tid, name)) + # 如果名称更新了,也可以 update + self.cursor.execute('UPDATE categories SET name = ? WHERE tid = ? AND name != ?', (name, tid, name)) + self.conn.commit() + except Exception as e: + print(f"[DB Error] save_category: {e}") + + def item_exists(self, vod_id): + with self.lock: + try: + self.cursor.execute('SELECT 1 FROM vods WHERE vod_id = ?', (vod_id,)) + return self.cursor.fetchone() is not None + except Exception as e: + print(f"[DB Error] item_exists: {e}") + return False + + def save_vod(self, data): + with self.lock: + try: + self.cursor.execute(''' + INSERT OR REPLACE INTO vods (vod_id, vod_name, type_id, vod_pic, vod_remarks, vod_content, crawled_at) + VALUES (?, ?, ?, ?, ?, ?, ?) + ''', ( + data.get('vod_id'), + data.get('vod_name'), + data.get('type_id'), + data.get('vod_pic'), + data.get('vod_remarks'), + data.get('vod_content'), + int(time.time()) + )) + vod_pk = self.cursor.lastrowid + if vod_pk == 0: + self.cursor.execute('SELECT id FROM vods WHERE vod_id = ?', (data.get('vod_id'),)) + res = self.cursor.fetchone() + if res: vod_pk = res[0] + + self.conn.commit() + return vod_pk + except Exception as e: + print(f"[DB Error] save_vod: {e}") + return None + + def save_episodes(self, vod_pk, episodes): + # 预处理 source_id 以减少锁内操作时间 + # 但 get_or_create_source 本身也加锁,所以这里可以先收集 + processed_eps = [] + for ep in episodes: + sid = self.get_or_create_source(ep['play_from']) + processed_eps.append((sid, ep['name'], ep['url'], ep.get('resolved_url', ''))) + + with self.lock: + try: + self.cursor.execute('DELETE FROM episodes WHERE vod_pk = ?', (vod_pk,)) + self.cursor.executemany(''' + INSERT INTO episodes (vod_pk, sid, name, raw_url, resolved_url) + VALUES (?, ?, ?, ?, ?) + ''', [(vod_pk, sid, name, raw_url, res_url) for sid, name, raw_url, res_url in processed_eps]) + self.conn.commit() + except Exception as e: + print(f"[DB Error] save_episodes: {e}") + + def close(self): + self.conn.close() + +# --- PHP 桥接调用 (PHP Bridge) --- +class PHPBridge: + def __init__(self, spider_path): + self.spider_path = spider_path + + def call(self, method, *args): + # 构建命令 + cmd = [PHP_CMD, BRIDGE_SCRIPT, self.spider_path, method] + cmd_args = [] + for arg in args: + if isinstance(arg, (dict, list)): + cmd_args.append(json.dumps(arg)) + else: + cmd_args.append(str(arg)) + cmd.extend(cmd_args) + + try: + # subprocess.run 是同步阻塞的,但在多线程中调用是安全的 + result = subprocess.run(cmd, capture_output=True, text=True, encoding='utf-8') + if result.returncode != 0: + if "Warning" not in result.stderr and "Notice" not in result.stderr: + pass + return None + + output = result.stdout.strip() + try: + json_res = json.loads(output) + if json_res['status'] == 'success': + return json_res['data'] + else: + return None + except json.JSONDecodeError: + return None + + except Exception as e: + print(f"[Bridge Error] {e}") + return None + +# --- 任务追踪器 (Task Tracker) --- +class TaskTracker: + def __init__(self): + self.lock = threading.Lock() + self.cond = threading.Condition(self.lock) + self.pending = 0 + + def add(self, n=1): + with self.lock: + self.pending += n + + def done(self): + with self.lock: + self.pending -= 1 + if self.pending == 0: + self.cond.notify_all() + + def wait_until_done(self): + with self.lock: + while self.pending > 0: + self.cond.wait() + +# --- 统计与监控 (Stats & Monitor) --- +class Stats: + def __init__(self): + self.lock = threading.Lock() + self.categories_found = 0 + self.pages_scanned = 0 + self.items_found = 0 + self.items_processed = 0 + self.items_skipped = 0 + self.episodes_resolved = 0 + self.errors = 0 + self.start_time = time.time() + + def inc(self, field, count=1): + with self.lock: + setattr(self, field, getattr(self, field) + count) + +# --- 爬虫逻辑 (Crawler Logic) --- +class Crawler: + def __init__(self, spider_path, db_path, max_pages=DEFAULT_MAX_PAGES, max_workers=DEFAULT_THREADS): + self.spider_path = spider_path + self.bridge = PHPBridge(spider_path) + self.db = DBManager(db_path) + self.max_pages = max_pages + self.max_workers = max_workers + self.stats = Stats() + self.executor = ThreadPoolExecutor(max_workers=max_workers) + self.tracker = TaskTracker() + self.running = True + + # 启动监控线程 + self.monitor_thread = threading.Thread(target=self.monitor_loop, daemon=True) + self.monitor_thread.start() + + def submit_task(self, func, *args): + self.tracker.add() + self.executor.submit(self._wrap_task, func, *args) + + def _wrap_task(self, func, *args): + try: + func(*args) + except Exception as e: + print(f"[Task Error] {e}") + self.stats.inc('errors') + finally: + self.tracker.done() + + def run(self): + print(f"🚀 开始并发爬取: {os.path.basename(self.spider_path)}") + print(f"⚙️ 配置: 最大线程={self.max_workers}, 每个分类最大页数={self.max_pages}, 解 析地址={RESOLVE_FINAL_URLS}") + + # 1. 获取首页分类 + home_data = self.bridge.call('homeContent', True) + if not home_data or 'class' not in home_data: + print("❌ 无法获取分类信息,退出。") + return + + categories = home_data['class'] + self.stats.categories_found = len(categories) + print(f"📋 获取到 {len(categories)} 个分类,开始派发任务...") + + # 2. 保存分类并派发分类任务 + for cat in categories: + tid = str(cat['type_id']) + name = cat['type_name'] + self.db.save_category(tid, name) + self.submit_task(self.process_category, tid, name) + + # 3. 等待所有任务完成 + self.tracker.wait_until_done() + self.running = False + + self.print_final_stats() + + # 关闭 executor 和 db + self.executor.shutdown(wait=True) + self.db.close() + + def monitor_loop(self): + while self.running: + self.print_progress() + time.sleep(1) + + def print_progress(self): + elapsed = time.time() - self.stats.start_time + speed = self.stats.items_processed / elapsed if elapsed > 0 else 0 + # \033[K 清除当前行剩余内容,确保更新时不会有残留字符 + sys.stdout.write( + f"\r\033[K⏱️ {elapsed:.1f}s | " + f"Pages: {self.stats.pages_scanned} | " + f"Items: {self.stats.items_processed}/{self.stats.items_found} | " + f"Skip: {self.stats.items_skipped} | " + f"Eps: {self.stats.episodes_resolved} | " + f"Speed: {speed:.2f} it/s | " + f"Err: {self.stats.errors}" + ) + sys.stdout.flush() + + def print_final_stats(self): + elapsed = time.time() - self.stats.start_time + print("\n" + "-" * 50) + print(f"统计报告:") + print(f" 总耗时: {elapsed:.2f} 秒") + print(f" 扫描页数: {self.stats.pages_scanned}") + print(f" 处理资源: {self.stats.items_processed}") + print(f" 跳过资源: {self.stats.items_skipped}") + print(f" 解析集数: {self.stats.episodes_resolved}") + print(f" 错误数量: {self.stats.errors}") + print("-" * 50) + + def process_category(self, tid, tname): + cat_data = self.bridge.call('categoryContent', tid, 1, False, {}) + + if not cat_data or 'list' not in cat_data: + self.stats.inc('errors') + return + + items = cat_data.get('list', []) + self.stats.inc('items_found', len(items)) + self.stats.inc('pages_scanned') + + for item in items: + item['type_id'] = tid + item['type_name'] = tname + self.submit_task(self.process_item, item) + + page_count = 0 + if 'pagecount' in cat_data: + try: + page_count = int(cat_data['pagecount']) + except: + page_count = 9999 + + # 递归触发第2页(如果需要) + # 如果明确返回只有1页,则停止;否则只要没达到max_pages就尝试下一页 + if page_count != 1: + next_page = 2 + if not self.max_pages or next_page <= self.max_pages: + self.submit_task(self.process_page, tid, tname, next_page) + + def process_page(self, tid, tname, page): + cat_data = self.bridge.call('categoryContent', tid, page, False, {}) + if not cat_data or 'list' not in cat_data: + self.stats.inc('errors') + return + + items = cat_data.get('list', []) + self.stats.inc('items_found', len(items)) + self.stats.inc('pages_scanned') + + if len(items) == 0: + return + + for item in items: + item['type_id'] = tid + item['type_name'] = tname + self.submit_task(self.process_item, item) + + # 提交下一页任务(递归爬取) + if len(items) > 0: + next_page = page + 1 + if not self.max_pages or next_page <= self.max_pages: + self.submit_task(self.process_page, tid, tname, next_page) + + def process_item(self, item): + vod_id = item['vod_id'] + vod_name = item['vod_name'] + + # 增量爬取检查:如果数据库中已存在该 vod_id,则跳过 + if self.db.item_exists(vod_id): + # 即使跳过,也可以尝试更新 type_id (如果之前为空) + # 但为了性能,这里暂时略过,除非强制更新 + self.stats.inc('items_skipped') + return + + # 详情页爬取 + detail_res = self.bridge.call('detailContent', [vod_id]) + if not detail_res or 'list' not in detail_res or not detail_res['list']: + self.stats.inc('errors') + return + + vod_data = detail_res['list'][0] + # 补全可能缺失的字段 + if 'vod_id' not in vod_data: vod_data['vod_id'] = vod_id + if 'type_id' not in vod_data: vod_data['type_id'] = item.get('type_id') + + # 存入 VOD 主表 + vod_pk = self.db.save_vod(vod_data) + if not vod_pk: + self.stats.inc('errors') + return + + self.stats.inc('items_processed') + + # 处理播放列表 + play_from_str = vod_data.get('vod_play_from', '') + play_url_str = vod_data.get('vod_play_url', '') + + if not play_from_str or not play_url_str: + return + + play_from_list = play_from_str.split('$$$') + play_url_list = play_url_str.split('$$$') + + all_episodes = [] + + for i, source_name in enumerate(play_from_list): + if i >= len(play_url_list): break + url_text = play_url_list[i] + + # 格式: 名字$地址#名字$地址 + episodes = url_text.split('#') + for ep_str in episodes: + if '$' in ep_str: + ep_name, ep_url = ep_str.split('$', 1) + else: + ep_name, ep_url = '正片', ep_str + + episode = { + 'play_from': source_name, + 'name': ep_name, + 'url': ep_url, + 'resolved_url': '' + } + + if RESOLVE_FINAL_URLS: + play_res = self.bridge.call('playerContent', source_name, ep_url, []) + if play_res and 'url' in play_res: + episode['resolved_url'] = play_res['url'] + self.stats.inc('episodes_resolved') + else: + pass + + all_episodes.append(episode) + + if all_episodes: + self.db.save_episodes(vod_pk, all_episodes) + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="DrPy PHP Spider Concurrent Crawler") + parser.add_argument("spider", nargs="?", default=DEFAULT_SPIDER, help="PHP spider file path") + parser.add_argument("-p", "--max-pages", type=int, default=DEFAULT_MAX_PAGES, help="Max pages per category") + parser.add_argument("-t", "--threads", type=int, default=DEFAULT_THREADS, help="Concurrency threshold (max workers)") + parser.add_argument("-n", "--no-resolve", action="store_true", help="Skip resolving final playback URLs") + + args = parser.parse_args() + + if args.no_resolve: + RESOLVE_FINAL_URLS = False + + spider_file = args.spider + if not os.path.exists(spider_file): + print(f"Error: File not found: {spider_file}") + sys.exit(1) + + # 根据爬虫文件名生成数据库文件名 (例如: spider.php -> spider.db) + # 确保数据库文件生成在爬虫文件同级目录 + spider_dir = os.path.dirname(os.path.abspath(spider_file)) + base_name = os.path.splitext(os.path.basename(spider_file))[0] + db_path = os.path.join(spider_dir, f"{base_name}.db") + + print(f"📁 数据库路径: {db_path}") + + crawler = Crawler(spider_file, db_path, args.max_pages, args.threads) + crawler.run() diff --git a/spider/php/index.php b/spider/php/index.php new file mode 100644 index 00000000..6960772f --- /dev/null +++ b/spider/php/index.php @@ -0,0 +1,16 @@ + 'ok', + 'message' => 'PHP 服务运行正常', + 'version' => PHP_VERSION, + 'platform' => 'Android', + 'time' => date('Y-m-d H:i:s'), + 'extensions' => get_loaded_extensions() +], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT); + + diff --git a/spider/php/lib/HtmlParser.php b/spider/php/lib/HtmlParser.php new file mode 100644 index 00000000..f96e5c68 --- /dev/null +++ b/spider/php/lib/HtmlParser.php @@ -0,0 +1,208 @@ +getDom($html); + $xpath = new DOMXPath($doc); + + $xpathQuery = $this->parseRuleToXpath($rule); + $nodes = $xpath->query($xpathQuery); + + $res = []; + if ($nodes) { + foreach ($nodes as $node) { + // saveHTML($node) returns OuterHTML + $res[] = $doc->saveHTML($node); + } + } + return $res; + } + + /** + * Parse HTML and return single value (Text, Html, or Attribute) + */ + public function pdfh($html, $rule, $baseUrl = '') { + if (empty($html) || empty($rule)) return ''; + $doc = $this->getDom($html); + $xpath = new DOMXPath($doc); + + // Separate Option + $option = ''; + if (strpos($rule, '&&') !== false) { + $parts = explode('&&', $rule); + $option = array_pop($parts); + $rule = implode('&&', $parts); + } + + $xpathQuery = $this->parseRuleToXpath($rule); + $nodes = $xpath->query($xpathQuery); + + if ($nodes && $nodes->length > 0) { + // Special handling for Text option: concatenate all nodes + if ($option === 'Text') { + $text = ''; + foreach ($nodes as $node) { + $text .= $node->textContent; + } + return $this->parseText($text); + } + + // For other options, use the first node + $node = $nodes->item(0); + return $this->formatOutput($doc, $node, $option, $baseUrl); + } + return ''; + } + + /** + * Parse HTML and return URL (auto joined) + */ + public function pd($html, $rule, $baseUrl = '') { + $res = $this->pdfh($html, $rule, $baseUrl); + return $this->urlJoin($baseUrl, $res); + } + + // --- Helper Methods --- + + private function parseText($text) { + // Match JS behavior: + // text = text.replace(/[\s]+/gm, '\n'); + // text = text.replace(/\n+/g, '\n').replace(/^\s+/, ''); + // text = text.replace(/\n/g, ' '); + + $text = preg_replace('/[\s]+/u', "\n", $text); + $text = preg_replace('/\n+/', "\n", $text); + $text = trim($text); + $text = str_replace("\n", ' ', $text); + return $text; + } + + private function parseRuleToXpath($rule) { + // Replace && with space to unify as descendant separator + $rule = str_replace('&&', ' ', $rule); + $parts = explode(' ', $rule); + $xpathParts = []; + + foreach ($parts as $part) { + if (empty($part)) continue; + $xpathParts[] = $this->transSingleSelector($part); + } + + // Join with descendant axis + return '//' . implode('//', $xpathParts); + } + + private function transSingleSelector($selector) { + // Handle :eq + $position = null; + if (preg_match('/:eq\((-?\d+)\)/', $selector, $matches)) { + $idx = intval($matches[1]); + $selector = str_replace($matches[0], '', $selector); + if ($idx >= 0) { + $position = $idx + 1; // XPath is 1-based + } else { + // -1 is last() + // -2 is last()-1 + $offset = abs($idx) - 1; + $position = "last()" . ($offset > 0 ? "-$offset" : ""); + } + } + + // Handle tag.class#id + $tag = '*'; + $conditions = []; + + // Extract id + if (preg_match('/#([\w-]+)/', $selector, $m)) { + $conditions[] = '@id="' . $m[1] . '"'; + $selector = str_replace($m[0], '', $selector); + } + + // Extract classes + if (preg_match_all('/\.([\w-]+)/', $selector, $m)) { + foreach ($m[1] as $cls) { + $conditions[] = 'contains(concat(" ", normalize-space(@class), " "), " ' . $cls . ' ")'; + } + $selector = preg_replace('/\.[\w-]+/', '', $selector); + } + + // Remaining is tag + if (!empty($selector)) { + $tag = $selector; + } + + $xpath = $tag; + if (!empty($conditions)) { + $xpath .= '[' . implode(' and ', $conditions) . ']'; + } + if ($position !== null) { + $xpath .= '[' . $position . ']'; + } + + return $xpath; + } + + private function formatOutput($doc, $node, $option, $baseUrl) { + if ($option === 'Text') { + return $this->parseText($node->textContent); + } elseif ($option === 'Html') { + return $doc->saveHTML($node); + } elseif ($option) { + // Attribute + $val = $node->getAttribute($option); + // Handle style url() extraction if needed? JS does it. + // JS: if (contains(opt, 'style') && contains(ret, 'url(')) ... + return $val; + } + // Default to outer HTML if no option provided + return $doc->saveHTML($node); + } + + private function getDom($html) { + $doc = new DOMDocument(); + // Suppress warnings for malformed HTML + libxml_use_internal_errors(true); + // Force UTF-8 encoding + if (!empty($html) && mb_detect_encoding($html, 'UTF-8', true) === false) { + $html = mb_convert_encoding($html, 'UTF-8', 'GBK, BIG5'); + } + // Add meta charset to ensure DOMDocument treats it as UTF-8 + $html = '' . $html; + + $doc->loadHTML($html); + libxml_clear_errors(); + return $doc; + } + + private function urlJoin($baseUrl, $relativeUrl) { + if (empty($relativeUrl)) return ''; + if (preg_match('#^https?://#', $relativeUrl)) return $relativeUrl; + + if (empty($baseUrl)) return $relativeUrl; + + $parts = parse_url($baseUrl); + $scheme = isset($parts['scheme']) ? $parts['scheme'] . '://' : 'http://'; + $host = isset($parts['host']) ? $parts['host'] : ''; + + // Handle protocol-relative URLs (starting with //) + if (substr($relativeUrl, 0, 2) == '//') { + return (isset($parts['scheme']) ? $parts['scheme'] . ':' : 'http:') . $relativeUrl; + } + + if (substr($relativeUrl, 0, 1) == '/') { + return $scheme . $host . $relativeUrl; + } + + // Relative path + $path = isset($parts['path']) ? $parts['path'] : '/'; + $dir = rtrim(dirname($path), '/\\'); + if ($dir === '/' || $dir === '\\') $dir = ''; // handle root + + return $scheme . $host . $dir . '/' . $relativeUrl; + } +} diff --git a/spider/php/lib/spider.php b/spider/php/lib/spider.php new file mode 100644 index 00000000..6a7d7b55 --- /dev/null +++ b/spider/php/lib/spider.php @@ -0,0 +1,396 @@ + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', + 'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8', + 'Accept-Language' => 'zh-CN,zh;q=0.9', + ]; + + /** + * @var HtmlParser + */ + protected $htmlParser; + + public function __construct() { + $this->htmlParser = new HtmlParser(); + } + + /** + * 初始化方法 + * @param string $extend 扩展参数 + */ + public function init($extend = '') { + // 子类实现 + } + + /** + * 获取首页分类 + * @param array $filter 筛选条件 + * @return array + */ + public function homeContent($filter) { + return ['class' => []]; + } + + /** + * 获取首页推荐视频 + * @return array + */ + public function homeVideoContent() { + return ['list' => []]; + } + + /** + * 获取分类详情 + * @param string $tid 分类ID + * @param int $pg 页码 + * @param array $filter 筛选条件 + * @param array $extend 扩展参数 + * @return array + */ + public function categoryContent($tid, $pg = 1, $filter = [], $extend = []) { + return ['list' => [], 'page' => $pg, 'pagecount' => 1, 'limit' => 20, 'total' => 0]; + } + + /** + * 获取视频详情 + * @param array $ids 视频ID列表 + * @return array + */ + public function detailContent($ids) { + return ['list' => []]; + } + + /** + * 搜索视频 + * @param string $key 关键词 + * @param bool $quick 快速搜索 + * @param int $pg 页码 + * @return array + */ + public function searchContent($key, $quick = false, $pg = 1) { + return ['list' => []]; + } + + /** + * 获取播放地址 + * @param string $flag 播放线路 + * @param string $id 视频播放ID + * @param array $vipFlags VIP标识 + * @return array + */ + public function playerContent($flag, $id, $vipFlags = []) { + return ['parse' => 0, 'url' => '', 'header' => []]; + } + + /** + * 代理请求 (可选) + * @param array $params + * @return mixed + */ + public function localProxy($params) { + return null; + } + + /** + * 执行 Action (可选) + * @param string $action 动作名称 + * @param string $value 参数值 + * @return mixed + */ + public function action($action, $value) { + return ''; + } + + // ================== 辅助方法 ================== + + protected function pdfa($html, $rule) { + return $this->htmlParser->pdfa($html, $rule); + } + + protected function pdfh($html, $rule, $baseUrl = '') { + return $this->htmlParser->pdfh($html, $rule, $baseUrl); + } + + protected function pd($html, $rule, $baseUrl = '') { + if (empty($baseUrl)) { + $baseUrl = $this->tryGetHost(); + } + return $this->htmlParser->pd($html, $rule, $baseUrl); + } + + /** + * 尝试获取子类定义的 HOST 常量或属性 + */ + private function tryGetHost() { + try { + $ref = new ReflectionClass($this); + + // 1. 尝试获取 HOST 属性 (优先) + if ($ref->hasProperty('HOST')) { + $prop = $ref->getProperty('HOST'); + // PHP 8.1+ 默认可访问私有属性,只有旧版本需要手动开启 + if (PHP_VERSION_ID < 80100) { + $prop->setAccessible(true); + } + $val = $prop->getValue($this); + if (!empty($val)) { + return $val; + } + } + + // 2. 尝试获取 const HOST 常量 + if ($ref->hasConstant('HOST')) { + return $ref->getConstant('HOST'); + } + } catch (Exception $e) { + // ignore + } + return ''; + } + + /** + * 快速构建分页返回结果 + * @param array $list 视频列表 + * @param int $pg 当前页码 + * @param int $total 总记录数 (可选) + * @param int $limit 每页条数 (默认 20) + * @return array + */ + protected function pageResult($list, $pg, $total = 0, $limit = 20) { + $pg = max(1, intval($pg)); + $count = count($list); + + if ($total > 0) { + $pagecount = ceil($total / $limit); + } else { + // 如果没有提供 total,尝试根据当前列表数量估算 + if ($count < $limit) { + // 当前页数据少于限制,说明是最后一页 + $pagecount = $pg; + $total = ($pg - 1) * $limit + $count; + } else { + // 还有下一页,设置一个较大的页数 + $pagecount = 9999; + $total = 99999; + } + } + + return [ + 'list' => $list, + 'page' => $pg, + 'pagecount' => intval($pagecount), + 'limit' => intval($limit), + 'total' => intval($total) + ]; + } + + /** + * 封装 HTTP 请求 + * @param string $url 请求地址 + * @param array $options CURL 选项 + * @param array $headers 请求头 + * @return string|bool + */ + protected function fetch($url, $options = [], $headers = []) { + // 支持从 options 中传递 headers + if (isset($options['headers'])) { + $headers = array_merge($headers, $options['headers']); + unset($options['headers']); + } + + $ch = curl_init(); + + // 1. 解析自定义 header 为关联数组 + $customHeaders = []; + foreach ($headers as $k => $v) { + if (is_numeric($k)) { + // 处理 "Key: Value" 格式 + $parts = explode(':', $v, 2); + if (count($parts) === 2) { + $key = trim($parts[0]); + $value = trim($parts[1]); + $customHeaders[$key] = $value; + } + } else { + $customHeaders[$k] = $v; + } + } + + // 2. 合并请求头 (自定义覆盖默认) + $finalHeadersMap = array_merge($this->headers, $customHeaders); + + // 3. 转换回 CURL 所需的索引数组 + $mergedHeaders = []; + foreach ($finalHeadersMap as $k => $v) { + if ($v === "") { + // To send empty header in CURL, use "Header;" (no colon) + $mergedHeaders[] = $k . ";"; + } else { + $mergedHeaders[] = "$k: $v"; + } + } + + $defaultOptions = [ + CURLOPT_URL => $url, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_SSL_VERIFYPEER => false, + CURLOPT_SSL_VERIFYHOST => false, + CURLOPT_FOLLOWLOCATION => true, + CURLOPT_TIMEOUT => 15, + CURLOPT_ENCODING => '', // 支持 GZIP 自动解压 + CURLOPT_HTTPHEADER => $mergedHeaders, + ]; + + // 处理 POST 数据 + if (isset($options['body'])) { + $defaultOptions[CURLOPT_POST] = true; + $defaultOptions[CURLOPT_POSTFIELDS] = $options['body']; + unset($options['body']); + } + + // 处理 Cookie + if (isset($options['cookie'])) { + $defaultOptions[CURLOPT_COOKIE] = $options['cookie']; + unset($options['cookie']); + } + + // 合并用户自定义选项 + foreach ($options as $k => $v) { + $defaultOptions[$k] = $v; + } + + curl_setopt_array($ch, $defaultOptions); + $result = curl_exec($ch); + + if (is_resource($ch)) { + curl_close($ch); + } + + return $result; + } + + protected function fetchJson($url, $options = []) { + $resp = $this->fetch($url, $options); + return json_decode($resp, true) ?: []; + } + + /** + * 自动运行,处理路由 + */ + public function run() { + if (defined('DRPY_BRIDGE')) { + return; + } + $ac = $_GET['ac'] ?? ''; + $t = $_GET['t'] ?? ''; + $pg = $_GET['pg'] ?? '1'; + $wd = $_GET['wd'] ?? ''; + $ids = $_GET['ids'] ?? ''; + $play = $_GET['play'] ?? ''; // 某些源使用 play 参数传递播放ID + $flag = $_GET['flag'] ?? ''; // 播放线路 + $filter = isset($_GET['filter']) && $_GET['filter'] === 'true'; // 是否过滤 + $extend = $_GET['ext'] ?? ''; // 扩展参数 + if (!empty($extend) && is_string($extend)) { + $decoded = json_decode(base64_decode($extend), true); + if (is_array($decoded)) { + $extend = $decoded; + } + } + $action = $_GET['action'] ?? ''; // Action 动作 + $value = $_GET['value'] ?? ''; // Action 参数 + + $this->init($extend); + + try { + // 0. Action (优先处理) + if ($ac === 'action') { + echo json_encode($this->action($action, $value), JSON_UNESCAPED_UNICODE); + return; + } + + // 1. 播放 (Play) + // 优先检测 play 参数或 ac=play + if ($ac === 'play' || !empty($play)) { + $playId = !empty($play) ? $play : ($_GET['id'] ?? ''); + echo json_encode($this->playerContent($flag, $playId), JSON_UNESCAPED_UNICODE); + return; + } + + // 2. 搜索 (Search) + // 有 wd 则是搜索 + if (!empty($wd)) { + echo json_encode($this->searchContent($wd, false, $pg), JSON_UNESCAPED_UNICODE); + return; + } + + // 3. 详情 (Detail) + // 有 ids 且 ac 不为空 + if (!empty($ids) && !empty($ac)) { + // ids 可能是逗号分隔的字符串 + $idList = explode(',', $ids); + echo json_encode($this->detailContent($idList), JSON_UNESCAPED_UNICODE); + return; + } + + // 4. 分类 (Category) + // 有 t 且 ac 不为空 + if ($t !== '' && !empty($ac)) { + // 处理 filter + $filterData = []; // 暂未实现复杂 filter 解析,可根据需要扩展 + echo json_encode($this->categoryContent($t, $pg, $filterData, $extend), JSON_UNESCAPED_UNICODE); + return; + } + + // 5. 首页 (默认) + // 通常返回 {class: [...], list: [...]} + // 可以分别调用 homeContent 和 homeVideoContent 合并 + $homeData = $this->homeContent($filter); + $videoData = $this->homeVideoContent(); + + $result = [ + 'class' => $homeData['class'] ?? [], + ]; + + // 如果 homeContent 只有 class,合并 homeVideoContent 的 list + if (isset($videoData['list'])) { + $result['list'] = $videoData['list']; + } + // 如果 homeContent 也有 list,优先使用 homeContent 的 list (视具体逻辑而定,这里简单的合并) + if (isset($homeData['list']) && !empty($homeData['list'])) { + $result['list'] = $homeData['list']; + } + // 兼容:如果 homeContent 返回了 filters + if (isset($homeData['filters'])) { + $result['filters'] = $homeData['filters']; + } + + echo json_encode($result, JSON_UNESCAPED_UNICODE); + + } catch (Exception $e) { + echo json_encode(['code' => 500, 'msg' => $e->getMessage()], JSON_UNESCAPED_UNICODE); + } catch (Throwable $e) { + echo json_encode(['code' => 500, 'msg' => $e->getMessage()], JSON_UNESCAPED_UNICODE); + } + } +} diff --git a/spider/php/readme.md b/spider/php/readme.md new file mode 100644 index 00000000..c951ac71 --- /dev/null +++ b/spider/php/readme.md @@ -0,0 +1,448 @@ +# PHP Spider 开发与维护指南 (DZ 风格) + +本文档总结了基于 `spider.php` 框架开发、调试、转换 PHP 爬虫源的核心经验与最佳实践。旨在帮助开发者快速上手,并作为后续开发的参考手册。 + +## 0. 环境搭建 (Windows) + +为了运行和调试 PHP 爬虫,需要在本地配置 PHP 环境。推荐使用 PHP 8.3+ NTS (Non Thread Safe) 版本。 + +### 0.1 下载与安装 +1. **下载 PHP**: + 可以直接点击下载推荐版本: + [php-8.3.29-nts-Win32-vs16-x64.zip](https://windows.php.net/downloads/releases/php-8.3.29-nts-Win32-vs16-x64.zip) +2. **解压**: + 将下载的压缩包解压到固定目录,例如 `C:\php` (建议路径不包含空格和中文)。 +3. **配置环境变量**: + - 右键 "此电脑" -> "属性" -> "高级系统设置" -> "环境变量"。 + - 在 "系统变量" 中找到 `Path`,点击 "编辑"。 + - 点击 "新建",输入你的 PHP 解压路径 (如 `C:\php`)。 + - 连续点击 "确定" 保存设置。 + +### 0.2 配置 php.ini +1. 进入 PHP 解压目录,找到 `php.ini-development` 文件,复制一份并重命名为 `php.ini`。 +2. 使用文本编辑器打开 `php.ini`,查找并修改以下配置 (去掉行首的 `;` 分号以启用): + ```ini + ; 指定扩展目录 + extension_dir = "ext" + + ; 启用核心扩展 (爬虫必须) + extension=curl + extension=mbstring + extension=openssl + extension=sockets + extension=sqlite3 + ``` +3. **验证安装**: + 打开新的 CMD 或 PowerShell 窗口,输入 `php -v`。 + 如果看到类似 `PHP 8.3.29 (cli) ...` 的输出,即表示环境配置成功。 + +## 0.5 环境搭建 (Linux/Ubuntu - 升级至 PHP 8.3) + +如果您在 Linux 环境(如 Ubuntu/Debian)下使用,建议通过 PPA 源安装或升级到 PHP 8.3。 + +### 0.5.1 卸载旧版 (可选) +如果系统中已安装旧版(如 PHP 8.1),建议先卸载以避免冲突: +```bash +sudo apt purge php8.1* -y +sudo apt autoremove -y +``` + +### 0.5.2 添加 PPA 源 +使用 Ondřej Surý 的 PPA 源获取最新 PHP 版本: +```bash +sudo apt install software-properties-common -y +sudo add-apt-repository ppa:ondrej/php -y +sudo apt update +``` + +### 0.5.3 安装 PHP 8.3 及扩展 +安装 CLI 版本及 Drpy 爬虫所需的常用扩展 (curl, mbstring, xml, mysql 等): +```bash +# 注意:openssl 通常已包含在核心或 common 包中,无需单独指定 php8.3-openssl +sudo apt install php8.3-cli php8.3-curl php8.3-mbstring php8.3-xml php8.3-mysql php8.3-sqlite3 -y +``` + +### 0.5.4 验证安装 +```bash +php -v +# 输出应显示 PHP 8.3.x +``` + +### 0.5.5 改init +```bash +php --ini +cd /etc/php/8.3/cli +vi php.ini +# 找到 extension=sqlite3 并取消注释(用到下面安装命令安装完了会自动配置好,这里还是给注释掉) +# 0.5.3已经包含了下面的命令,可以不管了 +apt-get install php8.3-sqlite3 +``` + +## 1. 核心架构与工具 + +### 1.1 基础框架 (`lib/spider.php`) +核心框架文件现已移动至 `lib` 目录。 +所有源必须包含 `lib/spider.php` 并继承 `BaseSpider` 类(通常在源文件中定义为 `class Spider extends BaseSpider`)。 + +**引用规范**: +```php +require_once __DIR__ . '/lib/spider.php'; +``` + +核心方法包括: +- `init()`: 初始化(可选)。 +- `homeContent($filter)`: 获取首页分类与筛选配置。 +- `categoryContent($tid, $pg, $filter, $extend)`: 获取分类列表数据。 +- `detailContent($ids)`: 获取视频详情与播放列表。 +- `searchContent($key, $quick, $pg)`: 搜索视频。 +- `playerContent($flag, $id, $vipFlags)`: 解析真实播放链接。 + +### 1.2 文件命名与目录规范 +- **源文件命名**: 统一使用 ` ᵈᶻ.php` 后缀(注意包含空格),例如 `果果 ᵈᶻ.php`。对于特定类型,建议增加标识:小说使用 `[书]`,漫画使用 `[画]`,例如 `七猫小说 ᵈᶻ[书].php`。 +- **系统文件排除**: `config.php` 会自动忽略以下文件: + - 系统文件 (`index.php`, `test_runner.php` 等) + - 以 `_` 开头的文件 (如 `_backup.php`) + - `config` 开头的文件 + - `lib` 目录下的文件 + +### 1.3 测试工具 (`test_runner.php`) +用于本地验证源的接口功能。 + +**用法**: +```bash +php test_runner.php "e:\php_work\php\荐片影视 ᵈᶻ.php" +``` +*(注意:由于文件名包含空格,命令行中路径建议加引号)* + +**测试流程**: +1. **首页测试**: 检查分类是否获取成功,筛选条件是否解析。 +2. **分类测试**: 选取第一个分类,获取第一页数据,检查 `vod_id` 和 `vod_name`。 +3. **详情测试**: 使用分类接口返回的 `vod_id`,检查详情信息及播放列表解析。 +4. **搜索测试**: 使用分类接口获取的名称进行搜索验证。 +5. **播放测试**: 选取第一个播放源,尝试解析播放链接。 + +--- + +## 2. 开发最佳实践 + +### 2.1 分页标准化 (`$this->pageResult`) +不要手动拼接复杂的 JSON 返回结构。使用框架内置的辅助方法 `$this->pageResult`。 + +**推荐写法**: +```php +$videos = []; +foreach ($items as $item) { + $videos[] = [ + 'vod_id' => $item['id'], + 'vod_name' => $item['name'], + 'vod_pic' => $item['pic'], + 'vod_remarks' => $item['remarks'] + ]; +} +return $this->pageResult($videos, $page, $total, $pageSize); +``` + +### 2.2 数据传递技巧 (`vod_id` 组合) +有时 `categoryContent` 到 `detailContent` 需要传递额外参数(如 `typeId`),但 `vod_id` 只能是字符串。 +**技巧**: 使用分隔符组合参数。 +```php +// 在 categoryContent 中 +'vod_id' => $id . '*' . $typeId + +// 在 detailContent 中 +$parts = explode('*', $ids[0]); +$id = $parts[0]; +$typeId = $parts[1] ?? ''; +``` + +### 2.3 HTML 解析 (DOMDocument) +处理 HTML 页面时,推荐使用 `DOMDocument` + `DOMXPath`,比正则更稳定。 +**IDE 爆红修复**: +IDE 经常提示 `getAttribute` 方法不存在,因为 DOMNode 不一定是 Element。 +**正确写法**: +```php +$node = $xpath->query('//img')->item(0); +if ($node instanceof DOMElement) { // 加上类型检查 + $pic = $node->getAttribute('src'); +} +``` + +### 2.4 加密与解密 (JS -> PHP 转换) +遇到 JS 源使用了加密(如 RSA, AES),需要用 PHP 的 `openssl` 扩展对应实现。 + +**案例:RSA 分块解密 (参考 `零度影视 ᵈᶻ.php`)** +PHP 的 `openssl_private_decrypt` 有长度限制(通常 117 或 128 字节)。如果密文过长,必须**分块解密**。 + +```php +private function rsaDecrypt($data) { + $decoded = base64_decode($data); + $keyRes = openssl_pkey_get_private($this->privateKey); + $details = openssl_pkey_get_details($keyRes); + $keySize = ceil($details['bits'] / 8); // e.g., 128 bytes + + $result = ''; + $chunks = str_split($decoded, $keySize); // 按密钥长度分块 + + foreach ($chunks as $chunk) { + if (openssl_private_decrypt($chunk, $decrypted, $this->privateKey, OPENSSL_PKCS1_PADDING)) { + $result .= $decrypted; + } + } + return $result; +} +``` + +### 2.5 CURL Header 空值处理 +在 PHP CURL 中,如果需要发送一个值为空的 Header(如 `Authorization:`),**不能**使用 `"Header: "`(带空格)或 `"Header:"`(不带值),这可能导致 Header 被忽略或发送错误的格式。 + +**正确做法**: 使用分号结尾。 +```php +$headers = [ + 'Authorization;', // 发送 "Authorization:" 头,值为空 + 'User-Agent: ...' +]; +``` +此技巧在移植七猫小说时解决了个别接口(如章节内容)验签失败的问题。 + +### 2.6 HtmlParser 与 pd 函数的智能 UrlJoin +在使用 `pd()` 函数提取链接(如图片 src、详情页 href)时,通常需要传入当前页面的 URL 作为 `baseUrl` 以便拼接相对路径。 + +**手动传入 (推荐用于详情页)**: +```php +$pic = $this->pd($html, 'img&&src', $currentUrl); +``` + +**自动识别 (推荐用于列表页)**: +如果你的 Spider 类定义了 `const HOST` 或 `$HOST` 属性,`pd()` 函数在未传入 `baseUrl` 时会自动使用它作为基准。 +```php +class Spider extends BaseSpider { + private const HOST = 'https://www.example.com'; + // ... + // 这里不需要传 $url,会自动用 HOST 拼接 + $pic = $this->pd($itemHtml, 'img&&src'); +} +``` + +### 2.7 IDE 兼容性与反射技巧 +在基类中访问子类的私有常量/属性(如 `$this->HOST`)时,直接访问会导致 IDE 报错(Undefined property)。 +**最佳实践**: 使用 `ReflectionClass` 动态获取。 +```php +$ref = new ReflectionClass($this); +if ($ref->hasConstant('HOST')) { + return $ref->getConstant('HOST'); +} +``` +这不仅消除了 IDE 警告,还支持了对 `private/protected` 属性的访问(需配合 `setAccessible(true)`,注意 PHP 8.1+ 已默认支持)。 + +--- + +## 3. HtmlParser 解析函数指南 + +为了与 JS 源(Hiker 规则)保持一致,我们在 `BaseSpider` 中内置了 `pdfa`, `pdfh`, `pd` 三个核心函数。它们支持 CSS 选择器风格的解析规则,并自动处理 DOM 操作。 + +### 3.1 规则语法 (Rule Syntax) +- **层级**: 使用 `&&` 分隔层级(在 XPath 中对应 `//`)。例如 `div.list&&ul&&li`。 +- **属性/选项**: 规则的**最后一部分**指定要获取的内容。 + - `Text`: 获取纯文本(自动去除首尾空格和多余换行)。 + - `Html`: 获取元素的 OuterHTML。 + - `src`, `href`, `data-id`, ...: 获取指定属性值。 +- **选择器**: + - `tag`: 标签名,如 `div`, `a`, `img`。 + - `.class`: 类名,如 `.title`。 + - `#id`: ID,如 `#content`。 + - `:eq(n)`: 索引选择(0 起始)。`:eq(0)` 是第一个,`:eq(-1)` 是最后一个。 + - 组合: `div.item:eq(0)`。 + +### 3.2 pdfa (Parse DOM For Array) +**用途**: 解析列表,返回 HTML 字符串数组。通常用于 `categoryContent` 中解析视频列表。 + +**签名**: +```php +protected function pdfa(string $html, string $rule): array +``` + +**示例**: +```php +// 获取所有 ul 下的 li 元素的 HTML +$items = $this->pdfa($html, 'ul.list&&li'); +foreach ($items as $itemHtml) { + // 在循环中继续使用 pdfh/pd 解析具体字段 +} +``` + +### 3.3 pdfh (Parse DOM For Html/Text) +**用途**: 解析单个节点的内容(文本、HTML 或属性)。 + +**签名**: +```php +protected function pdfh(string $html, string $rule, string $baseUrl = ''): string +``` + +**示例**: +```php +// 获取标题文本 +$title = $this->pdfh($itemHtml, '.title&&Text'); + +// 获取描述(可能包含 HTML 标签) +$desc = $this->pdfh($itemHtml, '.desc&&Html'); + +// 获取自定义属性 +$dataId = $this->pdfh($itemHtml, 'a&&data-id'); +``` + +### 3.4 pd (Parse DOM for Url) +**用途**: 解析链接(图片、跳转链接),并**自动进行 URL 拼接**(UrlJoin)。 + +**签名**: +```php +protected function pd(string $html, string $rule, string $baseUrl = ''): string +``` + +**特点**: +- 等同于 `pdfh` + `urlJoin`。 +- 如果规则末尾是属性(如 `src`, `href`),会自动基于 `$baseUrl` 转换为绝对路径。 +- 如果未传入 `$baseUrl`,会自动尝试读取类常量 `HOST`。 + +**示例**: +```php +// 自动拼接 HOST (假设类中定义了 const HOST) +$pic = $this->pd($itemHtml, 'img&&src'); + +// 手动指定 BaseUrl (如详情页解析推荐列表) +$link = $this->pd($html, 'a.next&&href', 'https://m.example.com/list/'); +``` + +--- + +## 4. 常见问题排查 + +- **Q: 为什么搜不到结果?** + - A: 检查 `searchContent` 的 URL 参数是否正确编码。特别是中文关键词,部分站点需要 URL 编码,部分不需要。 +- **Q: 详情页没有章节?** + - A: 很多小说/漫画源的详情页接口 (`/detail`) 返回的信息不全,通常需要额外调用章节列表接口 (`/chapter-list` 或类似)。务必抓包确认。 +- **Q: 图片加载失败?** + - A: 检查图片链接是否为相对路径。如果是,请确保在 `pd()` 或手动处理时进行了完整的 URL 拼接。 +- **Q: 验签失败?** + - A: 仔细比对 Python/JS 源的签名逻辑。注意参数排序(`ksort`)、空值处理、特殊字符编码差异。PHP 的 `md5` 输出默认是小写 hex。 + +--- + +## 5. Flutter 环境适配与 PHP 8.5+ 兼容性 + +在将 PHP 源部署到 Flutter 环境(如 TVBox 及其变种)并使用高版本 PHP (如 8.5.1) 时,可能会遇到类型严格性导致的兼容问题。 + +### 5.1 核心报错:`type 'String' is not a subtype of type 'int' of 'index'` +**现象**: +本地 `test_runner.php` 测试一切正常,但在 Flutter 端运行时报错,提示 String 类型无法作为 List 的索引。 + +**原因**: +PHP 脚本执行结束后**没有输出任何内容**。 +- `test_runner.php` 是手动实例化类并调用方法,所以能拿到结果。 +- Flutter 端通过 CLI 调用 PHP 脚本,如果脚本末尾没有主动调用运行逻辑,输出为空字符串。 +- 适配层收到空字符串后,可能默认处理为 `[]` (空 List)。后续逻辑尝试以 Map 方式(如 `['class']`)访问这个 List 时,就会触发 Dart 的类型错误。 + +**解决方案**: +确保每个源文件末尾都包含自动运行指令: +```php +// 必须在文件末尾加入此行 +(new Spider())->run(); +``` + +### 5.2 严格类型处理 (JSON 空对象) +**现象**: +PHP 的空数组 `[]` 在 `json_encode` 时默认为 `[]` (List)。如果在 PHP 8.5+ 环境下,客户端期望的是 Map `{}` (Object),可能会导致解析错误或类型不匹配。 + +**解决方案**: +对于明确应该是对象的字段(如 `header`, `filters`, `ext`),如果为空,必须强制转换为 Object。 +```php +// 错误 (输出 []) +'header' => [] + +// 正确 (输出 {}) +'header' => (object)[] +``` + +### 5.3 HTTPS 与 SSL 证书验证 +**现象**: +在某些 Flutter 环境或 Android 设备上,cURL 请求 HTTPS 站点失败,无返回或报错。这是 because 系统证书库可能不完整或 curl 配置过严。 + +**解决方案**: +显式关闭 SSL 证书校验。`BaseSpider` 的 `fetch` 方法已默认处理,但在重写 `fetch` 或使用原生 cURL 时需注意: +```php +curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); +curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); +``` + +### 5.4 健壮性建议 +1. **JSON 解析容错**:`json_decode($str ?: '{}', true)`,避免对空字符串解析报错。 +2. **空 ID 容错**:在 `detailContent` 或 `playerContent` 中,检查 ID 是否为空,避免向 API 发送非法请求导致崩溃。 + +--- + +## 6. 最近实战经验汇总 (2026/01 更新) + +### 6.1 漫画/图片源的标准协议 (`pics://`) +在开发漫画或图片类源时,`playerContent` 返回的 `url` 字段应使用 `pics://` 协议。 +- **格式**: `pics://图片链接1&&图片链接2&&图片链接3...` +- **注意**: 严禁使用非标准的 `mange://` 或其他自定义协议,除非客户端明确支持。使用 `pics://` 可确保通用播放器能正确识别为图片轮播模式。 + +### 6.2 静态资源智能过滤 +在解析漫画图片列表时,网页往往混杂大量的图标、LOGO、背景图或占位图(如 `grey.gif`)。必须建立过滤机制,否则会严重影响阅读体验。 + +**推荐过滤代码**: +```php +$uniqueImages = []; +foreach ($imageList as $img) { + // 1. 去重 + if (in_array($img, $uniqueImages)) continue; + + // 2. 关键词过滤 + if (strpos($img, "grey.gif") !== false) continue; // 占位图 + if (strpos($img, "logo") !== false) continue; // 网站LOGO + if (strpos($img, "icon") !== false) continue; // 图标 + if (strpos($img, "banner") !== false) continue; // 广告横幅 + + $uniqueImages[] = $img; +} +``` + +### 6.3 中文参数的 URL 编码陷阱 +PHP 的 `curl` 不会自动对 URL 中的非 ASCII 字符进行编码。如果 URL 中包含中文(如搜索关键词、分类标签),**必须**手动调用 `urlencode`。 +- **错误**: `$url = "https://site.com/search?q=" . $key;` +- **正确**: `$url = "https://site.com/search?q=" . urlencode($key);` +未编码会导致服务端返回 400 Bad Request 或 404。 + +### 6.4 `config.php` 类型定义 +在 `config.php` 中注册源时,请注意字段命名。 +- **正确**: `"类型": "小说"` 或 `"类型": "漫画"` +- **错误**: 不要使用 `"categories"` 或其他自定义字段名,否则前端可能无法正确分类显示。 + +### 6.5 PHP 8.5+ 与 Flutter JSON 深度兼容 +在 PHP 8.5.1 及 Flutter 混合环境下,JSON 格式的严谨性至关重要: +1. **空 Map 强制转换**: 任何应当输出为 `{}` 的字段(如 `filters`, `ext`, `header`),若为空数组,**必须**使用 `(object)[]` 或 `(object)$arr` 转换。否则 `json_encode` 会输出 `[]`,导致 Flutter 客户端报 `type 'String' is not a subtype of type 'int' of 'index'` 错误。 +2. **Undefined Index 防御**: 数组索引访问必须使用 `?? ''` 或 `?? []` 提供默认值(如 `$item['key'] ?? ''`)。PHP 的 Warning 信息若混入 JSON 输出,会直接导致解析失败。 + +### 6.6 HTTPS 强制适配 +Android 9+ 及 Flutter 应用默认禁止明文 HTTP 请求(Cleartext traffic not permitted)。 +- **最佳实践**: 在提取图片链接 (`vod_pic`) 时,检测并自动替换协议。 + ```php + if (strpos($pic, 'http://') === 0) { + $pic = str_replace('http://', 'https://', $pic); + } + ``` + +### 6.7 封面图片提取的高级策略 +针对结构复杂的详情页(如漫画站),单一规则往往不稳定: +1. **属性顺序无关正则**: 避免假设 `src` 在 `class` 之前或之后。使用更灵活的正则: + `/]*class=["\'](?:classA|classB)["\'][^>]*src=.../` +2. **多级回退机制**: + - **L1**: 优先从元数据区域(Metadata)提取。 + - **L2**: 若失败,尝试从内容区域(Content Block)提取第一张图。 + - **L3**: 若仍失败,全局搜索非 Icon/Logo/Gif 的第一张大图。 + +### 6.8 测试驱动开发 (TDD) 增强 +不要仅依赖人工查看。建议在 `test_runner.php` 中增加关键字段断言: +- **封面检查**: 在详情页测试中显式检查 `vod_pic` 是否为空,能提早发现 80% 的解析问题。 + +--- +*本文档更新于 2026/01/26,基于 Trae IDE 协作环境。* diff --git a/spider/php/test_runner.php b/spider/php/test_runner.php new file mode 100644 index 00000000..8c669ba5 --- /dev/null +++ b/spider/php/test_runner.php @@ -0,0 +1,251 @@ +run()) + // 防止污染后续的测试输出 + ob_start(); + require_once $file; + ob_end_clean(); + + if (!class_exists('Spider')) { + die("错误: 在文件 '$file' 中未找到 'Spider' 类\n"); + } + + echo "[初始化] 实例化 Spider 类...\n"; + $spider = new Spider(); + $spider->init(); + echo "[初始化] 完成\n\n"; + + // --- 1. 测试首页接口 (Home Interface) --- + echo ">>> [1/5] 测试首页接口 (homeContent)\n"; + $startTime = microtime(true); + $home = $spider->homeContent(true); + $cost = round((microtime(true) - $startTime) * 1000, 2); + + $classes = $home['class'] ?? []; + $filters = $home['filters'] ?? []; + + if (!empty($classes)) { + echo " ✅ 通过 (耗时: {$cost}ms)\n"; + echo " - 获取到 " . count($classes) . " 个分类\n"; + + // 打印前几个分类名称作为示例 + $classNames = array_column(array_slice($classes, 0, 5), 'type_name'); + echo " - 分类示例: " . implode(', ', $classNames) . (count($classes) > 5 ? ' ...' : '') . "\n"; + + if (!empty($filters)) { + $filterCount = is_object($filters) ? count(get_object_vars($filters)) : count($filters); + echo " - 包含筛选配置 (Filters): " . $filterCount . " 组\n"; + } + } else { + echo " ⚠️ 警告: 未获取到分类列表 (class 为空)\n"; + } + + // 确定用于测试分类接口的 type_id + $tid = $classes[0]['type_id'] ?? null; + $tname = $classes[0]['type_name'] ?? '未知分类'; + + if (!$tid && !empty($filters)) { + // 如果 class 为空但有 filters,尝试从 filters 获取 key + foreach ($filters as $key => $val) { + $tid = $key; + $tname = "FilterKey:$key"; + break; + } + } + + echo "\n"; + + // --- 2. 测试分类接口 (Category Interface) --- + $vodId = null; + $vodName = null; // 用于搜索测试 + if ($tid) { + echo ">>> [2/5] 测试分类接口 (categoryContent) - 测试分类: [$tname] (ID: $tid)\n"; + $startTime = microtime(true); + // 模拟传入 filter 参数为空 + $cat = $spider->categoryContent($tid, 1, false, []); + $cost = round((microtime(true) - $startTime) * 1000, 2); + + $list = $cat['list'] ?? []; + if (!empty($list)) { + echo " ✅ 通过 (耗时: {$cost}ms)\n"; + echo " - 获取到 " . count($list) . " 个资源\n"; + + $firstItem = $list[0]; + $vodId = $firstItem['vod_id'] ?? null; + $vodName = $firstItem['vod_name'] ?? '未知名称'; + echo " - 第一条数据: [$vodName] (ID: $vodId)\n"; + } else { + echo " ❌ 失败: 未返回资源列表 (list 为空)\n"; + } + } else { + echo ">>> [2/5] 测试分类接口: ⏭️ 跳过 (未找到有效的分类ID)\n"; + } + + echo "\n"; + + // --- 3. 测试详情接口 (Detail Interface) --- + $playUrl = null; + $playFrom = null; + + if ($vodId) { + echo ">>> [3/5] 测试详情接口 (detailContent) - 测试资源ID: $vodId\n"; + $startTime = microtime(true); + $detail = $spider->detailContent([$vodId]); + $cost = round((microtime(true) - $startTime) * 1000, 2); + + $detailList = $detail['list'] ?? []; + + if (!empty($detailList)) { + $vod = $detailList[0]; + $name = $vod['vod_name'] ?? '未知'; + // 更新 vodName,详情页的名称通常更准确 + if ($name && $name !== '未知') { + $vodName = $name; + } + $playUrl = $vod['vod_play_url'] ?? ''; + $playFrom = $vod['vod_play_from'] ?? ''; + $pic = $vod['vod_pic'] ?? ''; + $desc = $vod['vod_content'] ?? ''; + + echo " ✅ 通过 (耗时: {$cost}ms)\n"; + echo " - 资源名称: $name\n"; + echo " - 封面图片: " . ($pic ? $pic : "⚠️ 未获取到封面") . "\n"; + echo " - 播放源 (vod_play_from): $playFrom\n"; + + // 检查播放地址 + if (!empty($playUrl)) { + $urlCount = substr_count($playUrl, '$'); + // 粗略估计集数,通常每集是 名称$url + $episodeCount = $urlCount > 0 ? ($urlCount + 1) / 2 : 1; + // 或者直接按 # 分割统计播放列表数 + $playlistCount = substr_count($playFrom, '$$$') + 1; + + echo " - 播放列表数据长度: " . strlen($playUrl) . " 字符\n"; + // 简单展示部分播放链接 + $previewUrl = mb_substr($playUrl, 0, 50) . '...'; + echo " - 播放链接预览: $previewUrl\n"; + } else { + echo " ⚠️ 警告: vod_play_url 为空!\n"; + } + + if (!empty($desc)) { + echo " - 简介长度: " . mb_strlen($desc) . " 字\n"; + } + + } else { + echo " ❌ 失败: 未返回详情数据\n"; + } + } else { + echo ">>> [3/5] 测试详情接口: ⏭️ 跳过 (未找到有效的资源ID)\n"; + } + + echo "\n"; + + // --- 4. 测试搜索接口 (Search Interface) --- + // 使用之前获取到的 vodName 进行搜索,如果没有则使用默认关键词 "爱" + $searchKey = $vodName ?: "爱"; + echo ">>> [4/5] 测试搜索接口 (searchContent) - 关键词: [$searchKey]\n"; + + try { + $startTime = microtime(true); + $searchRes = $spider->searchContent($searchKey, false, 1); + $cost = round((microtime(true) - $startTime) * 1000, 2); + + $searchList = $searchRes['list'] ?? []; + if (!empty($searchList)) { + echo " ✅ 通过 (耗时: {$cost}ms)\n"; + echo " - 搜索到 " . count($searchList) . " 个结果\n"; + $firstSearch = $searchList[0]; + echo " - 第一条结果: " . ($firstSearch['vod_name'] ?? '未知') . "\n"; + } else { + echo " ⚠️ 警告: 搜索未返回结果 (但这不代表接口错误)\n"; + } + } catch (Throwable $e) { + echo " ⚠️ 异常: 搜索接口调用失败 (允许失败)\n"; + echo " 错误信息: " . $e->getMessage() . "\n"; + } + + echo "\n"; + + // --- 5. 测试播放接口 (Player Interface) --- + if ($playUrl && $playFrom) { + // 解析播放链接,取第一组的第一个链接 + // 格式通常是: 播放源1$$$集数1$链接1#集数2$链接2...$$$播放源2... + // 或者是: 集数1$链接1#集数2$链接2... + + // 简单处理:先按 $$$ 分割取第一个播放源对应的链接串 + $playUrls = explode('$$$', $playUrl); + $currentUrlBlock = $playUrls[0] ?? ''; + + // 再按 # 分割取第一集 + $episodes = explode('#', $currentUrlBlock); + $firstEp = $episodes[0] ?? ''; + + // 再按 $ 分割取链接 (通常是 名称$链接) + $parts = explode('$', $firstEp); + $targetUrl = end($parts); // 取最后一部分作为链接 + + // 播放源flag + $playFroms = explode('$$$', $playFrom); + $flag = $playFroms[0] ?? 'default'; + + echo ">>> [5/5] 测试播放接口 (playerContent) - Flag: [$flag]\n"; + echo " - 目标链接: $targetUrl\n"; + + try { + $startTime = microtime(true); + // $flag, $id, $vipFlags + $playerRes = $spider->playerContent($flag, $targetUrl, []); + $cost = round((microtime(true) - $startTime) * 1000, 2); + + if (!empty($playerRes)) { + echo " ✅ 通过 (耗时: {$cost}ms)\n"; + // 打印返回的关键字段 + $parse = $playerRes['parse'] ?? 'N/A'; + $url = $playerRes['url'] ?? 'N/A'; + $header = $playerRes['header'] ?? 'N/A'; + + echo " - Parse: $parse\n"; + echo " - PlayUrl: $url\n"; + if (is_array($header)) { + echo " - Header: " . json_encode($header, JSON_UNESCAPED_UNICODE) . "\n"; + } + } else { + echo " ⚠️ 警告: 播放接口返回为空\n"; + } + } catch (Throwable $e) { + echo " ⚠️ 异常: 播放接口调用失败 (允许失败)\n"; + echo " 错误信息: " . $e->getMessage() . "\n"; + } + } else { + echo ">>> [5/5] 测试播放接口: ⏭️ 跳过 (未获取到有效的播放链接或播放源信息)\n"; + } + +} catch (Throwable $e) { + echo "\n⛔ 严重错误 (CRITICAL ERROR):\n"; + echo " 信息: " . $e->getMessage() . "\n"; + echo " 位置: " . $e->getFile() . " 第 " . $e->getLine() . " 行\n"; + echo " 堆栈:\n" . $e->getTraceAsString() . "\n"; +} + +echo "==================================================\n"; +echo "测试结束\n"; diff --git "a/spider/php/\344\270\203\347\214\253\345\260\217\350\257\264 \341\265\210\341\266\273[\344\271\246].php" "b/spider/php/\344\270\203\347\214\253\345\260\217\350\257\264 \341\265\210\341\266\273[\344\271\246].php" new file mode 100644 index 00000000..407d2910 --- /dev/null +++ "b/spider/php/\344\270\203\347\214\253\345\260\217\350\257\264 \341\265\210\341\266\273[\344\271\246].php" @@ -0,0 +1,357 @@ +startPage = 1; + } + + public function homeContent($filter) { + $classes = [ + ['type_id' => 'a', 'type_name' => '全部'], + ['type_id' => '1', 'type_name' => '女生原创'], + ['type_id' => '0', 'type_name' => '男生原创'], + ['type_id' => '2', 'type_name' => '出版图书'] + ]; + + $filters = []; + // Filter URL pattern: {{fl.作品分类 or 'a'}}-a-{{fl.作品字数 or 'a'}}-{{fl.更新时间 or 'a'}}-a-{{fl.是否完结 or 'a'}}-{{fl.排序 or 'click'}} + // 注意 URL 结构: /shuku/{class}-{filter}-{page}/ + // class 是 type_id. + // filter string: type-a-word-time-a-status-sort + + $filterConfig = [ + 'key' => 'filters', + 'name' => '筛选', + 'value' => [ + ['n' => '作品分类', 'v' => 'type', 'init' => 'a', 'list' => [ + ['n' => '全部', 'v' => 'a'], + ['n' => '言情', 'v' => '7'], + ['n' => '都市', 'v' => '1'], + ['n' => '玄幻', 'v' => '8'], + ['n' => '战神', 'v' => '295'], + ['n' => '赘婿', 'v' => '298'], + ['n' => '神医', 'v' => '297'], + ['n' => '脑洞', 'v' => '253'], + ['n' => '悬疑', 'v' => '10'], + ['n' => '历史', 'v' => '2'], + ['n' => '武侠', 'v' => '4'], + ['n' => '游戏', 'v' => '5'], + ['n' => '科幻', 'v' => '6'], + ['n' => '现言', 'v' => '17'], + ['n' => '古言', 'v' => '13'], + ['n' => '穿越', 'v' => '23'], + ['n' => '重生', 'v' => '24'], + ['n' => '豪门', 'v' => '32'], + ['n' => '其他', 'v' => '11'], + ]], + ['n' => '作品字数', 'v' => 'word', 'init' => 'a', 'list' => [ + ['n' => '全部', 'v' => 'a'], + ['n' => '30万字以下', 'v' => '1'], + ['n' => '30-50万字', 'v' => '2'], + ['n' => '50-100万字', 'v' => '3'], + ['n' => '100-200万字', 'v' => '4'], + ['n' => '200万字以上', 'v' => '5'], + ]], + ['n' => '更新时间', 'v' => 'time', 'init' => 'a', 'list' => [ + ['n' => '全部', 'v' => 'a'], + ['n' => '3日内', 'v' => '1'], + ['n' => '7日内', 'v' => '2'], + ['n' => '半月内', 'v' => '3'], + ['n' => '一月内', 'v' => '4'], + ]], + ['n' => '是否完结', 'v' => 'status', 'init' => 'a', 'list' => [ + ['n' => '全部', 'v' => 'a'], + ['n' => '连载中', 'v' => '1'], + ['n' => '已完结', 'v' => '2'], + ]], + ['n' => '排序', 'v' => 'sort', 'init' => 'click', 'list' => [ + ['n' => '人气', 'v' => 'click'], + ['n' => '更新', 'v' => 'date'], + ['n' => '评分', 'v' => 'score'], + ]] + ] + ]; + + foreach ($classes as $class) { + $filters[$class['type_id']] = [$filterConfig]; + } + + return [ + 'class' => $classes, + 'filters' => (object)$filters + ]; + } + + public function categoryContent($tid, $pg = 1, $filter = [], $extend = []) { + // Filter logic: + // {{fl.作品分类 or 'a'}}-a-{{fl.作品字数 or 'a'}}-{{fl.更新时间 or 'a'}}-a-{{fl.是否完结 or 'a'}}-{{fl.排序 or 'click'}} + $f_type = $extend['type'] ?? 'a'; + $f_word = $extend['word'] ?? 'a'; + $f_time = $extend['time'] ?? 'a'; + $f_status = $extend['status'] ?? 'a'; + $f_sort = $extend['sort'] ?? 'click'; + + $filterStr = "{$f_type}-a-{$f_word}-{$f_time}-a-{$f_status}-{$f_sort}"; + + // URL: /shuku/{class}-{filter}-{page}/ + $url = sprintf(self::LIST_URL_TEMPLATE, $tid, $filterStr, $pg); + + $html = $this->fetch($url); + + $videos = []; + if ($html) { + $items = $this->pdfa($html, 'ul.qm-cover-text&&li'); + foreach ($items as $itemHtml) { + $video = [ + 'vod_id' => '', + 'vod_name' => $this->pdfh($itemHtml, '.s-tit&&Text'), + 'vod_pic' => $this->pd($itemHtml, 'img&&src', $url), + 'vod_remarks' => $this->pdfh($itemHtml, '.s-author&&Text'), + 'vod_content' => $this->pdfh($itemHtml, '.s-desc&&Text') + ]; + + $href = $this->pd($itemHtml, 'a&&href', $url); + if (preg_match('/shuku\/(\d+)/', $href, $matches)) { + $video['vod_id'] = $matches[1]; + } + + if (!empty($video['vod_id'])) { + $videos[] = $video; + } + } + } + + return $this->pageResult($videos, $pg); + } + + public function detailContent($ids) { + $id = $ids[0]; // This is book_id + $url = self::HOST . "/shuku/$id/"; + + // 1. Fetch Detail Page for basic info + $html = $this->fetch($url); + $vod = [ + 'vod_id' => $id, + 'vod_name' => '', + 'vod_pic' => '', + 'vod_content' => '', + 'vod_remarks' => '', + 'vod_director' => '', + 'vod_play_from' => '七猫小说', + ]; + + if ($html) { + $vod['vod_name'] = $this->pdfh($html, 'span.txt&&Text'); + $vod['vod_pic'] = $this->pd($html, '.wrap-pic&&img&&src', $url); + $vod['vod_content'] = $this->pdfh($html, '.book-introduction-item&&.qm-with-title-tb&&Text'); + $vod['vod_director'] = $this->pdfh($html, '.sub-title&&span&&a&&Text'); + $vod['vod_remarks'] = $this->pdfh($html, '.qm-tag&&Text'); + } + + // 2. Fetch Chapter List via API + // https://www.qimao.com/api/book/chapter-list?book_id=1699328 + $chapterUrl = self::HOST . "/api/book/chapter-list?book_id=$id"; + $json = $this->fetchJson($chapterUrl); + + $playList = []; + if (isset($json['data']['chapters'])) { + foreach ($json['data']['chapters'] as $ch) { + $title = $ch['title'] ?? ''; + $cid = $ch['id'] ?? ''; + // Format: title$book_id@@chapter_id@@title + $playList[] = "$title$$id@@$cid@@$title"; + } + } + + $vod['vod_play_url'] = implode('#', $playList); + return ['list' => [$vod]]; + } + + public function searchContent($key, $quick = false, $pg = 1) { + $params = [ + 'extend' => '', + 'tab' => '0', // Missing in previous version + 'gender' => '0', + 'refresh_state' => '8', // Missing in previous version + 'page' => $pg, + 'wd' => $key, + 'is_short_story_user' => '0' + ]; + + // Calculate Sign + $signStr = ""; + ksort($params); + foreach ($params as $k => $v) { + $signStr .= $k . "=" . $v; + } + $signStr .= self::SIGN_KEY; + $params['sign'] = md5($signStr); + + $url = self::SEARCH_URL . '?' . http_build_query($params); + // echo "DEBUG Search URL: $url\n"; + + $headers = $this->getSignHeaders(); + // Use fetch to see raw response + $raw = $this->fetch($url, ['headers' => $headers]); + // echo "DEBUG Search Response: " . substr($raw, 0, 200) . "\n"; + $json = json_decode($raw, true); + + $videos = []; + if (!empty($json['data']['books'])) { + foreach ($json['data']['books'] as $item) { + // Python filters by show_type == '0' + if (isset($item['show_type']) && $item['show_type'] == '0') { + $videos[] = [ + 'vod_id' => $item['id'], + 'vod_name' => $item['original_title'], + 'vod_pic' => $item['image_link'] ?? '', + 'vod_remarks' => $item['author'] ?? '', + 'vod_content' => $item['intro'] ?? '' + ]; + } + } + } + return [ + 'list' => $videos + ]; + } + + public function playerContent($flag, $id, $vipFlags = []) { + // id format: title$book_id@@chapter_id@@title + $parts = explode('@@', $id); + + // Use full ID part (Title$BookID) as in JS/Python source + $bookId = $parts[0]; + $chapterId = $parts[1] ?? ''; + $title = $parts[2] ?? ''; + + $params = [ + 'id' => $bookId, + 'chapterId' => $chapterId + ]; + + // Calculate Sign + $signStr = ""; + ksort($params); + foreach ($params as $k => $v) { + $signStr .= $k . "=" . $v; + } + $signStr .= self::SIGN_KEY; + $params['sign'] = md5($signStr); + + // Debug info + // echo "\nDEBUG Sign Str: $signStr\n"; + // echo "DEBUG Sign: " . $params['sign'] . "\n"; + + // Manual URL construction to match Python's order: id, chapterId, sign + // Although ksort is used for sign calculation, the request URL might need specific order + $query = 'id=' . $bookId . '&chapterId=' . $chapterId . '&sign=' . $params['sign']; + $url = self::CONTENT_URL . '?' . $query; + // echo "DEBUG URL: $url\n"; + + // Use BaseSpider fetch with specific options + $options = [ + CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, // Force HTTP/1.1 to match Python requests behavior + 'headers' => $this->getSignHeaders() + ]; + + $raw = $this->fetch($url, $options); + + // echo "DEBUG Response: " . substr($raw, 0, 100) . "\n"; + + $json = json_decode($raw, true) ?: []; + + $content = ''; + if (isset($json['data']['content'])) { + $content = $this->decodeContent($json['data']['content']); + } + + if (empty($content)) { + $msg = $json['msg'] ?? 'unknown error'; + $code = $json['code'] ?? 'unknown'; + $preview = substr($raw, 0, 100); + return [ + 'parse' => 0, + 'url' => 'novel://' . json_encode(['title' => "Error: $code - $msg ($preview)", 'content' => ''], JSON_UNESCAPED_UNICODE), + 'header' => (object)[] + ]; + } + + return [ + 'parse' => 0, + 'url' => 'novel://' . json_encode(['title' => $title, 'content' => $content], JSON_UNESCAPED_UNICODE), + 'header' => (object)[] + ]; + } + + // ================== Helpers ================== + + private function getSign($params) { + ksort($params); + $str = ""; + foreach ($params as $k => $v) { + $str .= $k . "=" . $v; + } + $str .= self::SIGN_KEY; + // Debug: return raw string for checking if needed, but for now just MD5 + // To debug: throw exception or log + return md5($str); + } + + private function getSignHeaders() { + return [ + "User-Agent" => "python-requests/2.31.0", // Mimic Python requests + "Accept" => "*/*", + "app-version" => "51110", + "platform" => "android", + "reg" => "0", + "AUTHORIZATION" => "", + "application-id" => "com.****.reader", + "net-env" => "1", + "channel" => "unknown", + "qm-params" => "", + "sign" => "fc697243ab534ebaf51d2fa80f251cb4" + ]; + } + + private function decodeContent($base64Response) { + // 1. Base64 Decode + $bin = base64_decode($base64Response); + if (!$bin) return ''; + + // 2. Extract IV (First 16 bytes) + // JS logic: txt = Base64.parse(resp).toString() (Hex string) + // iv = txt.slice(0, 32) (16 bytes hex) + // content = txt.slice(32) + // So raw binary: first 16 bytes are IV. + + $iv = substr($bin, 0, 16); + $data = substr($bin, 16); + + $key = hex2bin(self::AES_KEY_HEX); + + // 3. AES Decrypt + $decrypted = openssl_decrypt($data, 'AES-128-CBC', $key, OPENSSL_RAW_DATA, $iv); + return trim($decrypted); + } +} + +// 运行爬虫 +(new Spider())->run(); diff --git "a/spider/php/\344\272\272\344\272\272\345\275\261\350\247\206 \341\265\210\341\266\273.php" "b/spider/php/\344\272\272\344\272\272\345\275\261\350\247\206 \341\265\210\341\266\273.php" new file mode 100644 index 00000000..5678e47b --- /dev/null +++ "b/spider/php/\344\272\272\344\272\272\345\275\261\350\247\206 \341\265\210\341\266\273.php" @@ -0,0 +1,204 @@ +UA, + 'Origin: *', + 'Referer: https://docs.qq.com/', + 'Accept: application/json, text/plain, */*', + 'Accept-Language: zh-CN' + ]; + if ($isJson) { + $headers[] = 'Content-Type: application/json'; + } + return $headers; + } + + public function homeContent($filter) { + $classes = [ + ['type_id' => '1', 'type_name' => '电影'], + ['type_id' => '2', 'type_name' => '电视剧'], + ['type_id' => '3', 'type_name' => '综艺'], + ['type_id' => '5', 'type_name' => '动漫'], + ['type_id' => '4', 'type_name' => '纪录片'], + ['type_id' => '6', 'type_name' => '短剧'], + ['type_id' => '7', 'type_name' => '特别节目'], + ['type_id' => '8', 'type_name' => '少儿内容'] + ]; + + // 初始首页内容(空分类调用第一页数据) + $data = $this->categoryContent('', 1); + + return [ + 'class' => $classes, + 'list' => $data['list'] ?? [], + 'filters' => (object)[] + ]; + } + + public function categoryContent($tid, $pg = 1, $filter = [], $extend = []) { + $apiUrl = $this->HOST . '/api.php/main_program/moviesAll/'; + + $payload = [ + 'type' => (string)$tid, + 'sort' => 'vod_time', + 'area' => '', + 'style' => '', + 'time' => '', + 'pay' => '', + 'page' => $pg, + 'limit' => '60' + ]; + + $jsonStr = $this->fetch($apiUrl, [ + CURLOPT_POST => 1, + CURLOPT_POSTFIELDS => json_encode($payload), + CURLOPT_HTTPHEADER => $this->getHeaders(), + CURLOPT_SSL_VERIFYPEER => false, + CURLOPT_SSL_VERIFYHOST => false // 补全SSL校验关闭,避免HTTPS请求失败 + ]); + + $jsonObj = json_decode($jsonStr ?: '{}', true); + $list = []; + + if (isset($jsonObj['data']['list']) && is_array($jsonObj['data']['list'])) { + $list = $this->arr2vods($jsonObj['data']['list']); + } + // 补全total参数,适配分页逻辑 + $total = isset($jsonObj['data']['pagecount']) ? $jsonObj['data']['pagecount'] * 60 : 0; + + return $this->pageResult($list, $pg, $total, 60); + } + + public function detailContent($ids) { + $id = is_array($ids) ? ($ids[0] ?? '') : $ids; + if (empty($id)) return ['list' => []]; // 空ID容错 + + $apiUrl = $this->HOST . '/api.php/player/details/'; + + $payload = ['id' => (string)$id]; + + $jsonStr = $this->fetch($apiUrl, [ + CURLOPT_POST => 1, + CURLOPT_POSTFIELDS => json_encode($payload), + CURLOPT_HTTPHEADER => $this->getHeaders(), + CURLOPT_SSL_VERIFYPEER => false, + CURLOPT_SSL_VERIFYHOST => false + ]); + + $jsonObj = json_decode($jsonStr ?: '{}', true); + $vod = []; + + if (isset($jsonObj['detailData']) && is_array($jsonObj['detailData'])) { + $d = $jsonObj['detailData']; + $vod = [ + 'vod_id' => $d['vod_id'] ?? '', + 'vod_name' => $d['vod_name'] ?? '未知影片', + 'vod_pic' => $d['vod_pic'] ?? '', + 'vod_remarks' => $d['vod_remarks'] ?? '', + 'vod_year' => $d['vod_year'] ?? '', + 'vod_area' => $d['vod_area'] ?? '', + 'vod_actor' => $d['vod_actor'] ?? '', + 'vod_director' => $d['vod_director'] ?? '', + 'vod_content' => $d['vod_content'] ?? '暂无影片介绍', + 'vod_play_from' => $d['vod_play_from'] ?? '', + 'vod_play_url' => $d['vod_play_url'] ?? '', + 'type_name' => $d['vod_class'] ?? '' + ]; + } + + return ['list' => [$vod]]; + } + + public function searchContent($key, $quick = false, $pg = 1) { + if (empty($key) || $pg > 1) return $this->pageResult([], $pg, 0); + + $apiUrl = $this->HOST . '/api.php/search/syntheticalSearch/'; + $payload = ['keyword' => $key]; + + $jsonStr = $this->fetch($apiUrl, [ + CURLOPT_POST => 1, + CURLOPT_POSTFIELDS => json_encode($payload), + CURLOPT_HTTPHEADER => $this->getHeaders(), + CURLOPT_SSL_VERIFYPEER => false, + CURLOPT_SSL_VERIFYHOST => false + ]); + + $jsonObj = json_decode($jsonStr ?: '{}', true); + $videos = []; + + if (isset($jsonObj['data']) && is_array($jsonObj['data'])) { + $data = $jsonObj['data']; + if (!empty($data['chasingFanCorrelation']) && is_array($data['chasingFanCorrelation'])) { + $videos = array_merge($videos, $this->arr2vods($data['chasingFanCorrelation'])); + } + if (!empty($data['moviesCorrelation']) && is_array($data['moviesCorrelation'])) { + $videos = array_merge($videos, $this->arr2vods($data['moviesCorrelation'])); + } + } + + return $this->pageResult($videos, $pg, count($videos)); + } + + public function playerContent($flag, $id, $vipFlags = []) { + $apiUrl = $this->HOST . '/api.php/player/payVideoUrl/'; + $payload = ['url' => $id]; + + $jsonStr = $this->fetch($apiUrl, [ + CURLOPT_POST => 1, + CURLOPT_POSTFIELDS => json_encode($payload), + CURLOPT_HTTPHEADER => $this->getHeaders(), + CURLOPT_TIMEOUT => 30, + CURLOPT_SSL_VERIFYPEER => false + ]); + + $jsonObj = json_decode($jsonStr, true); + $url = $id; + $jx = 0; + + if (isset($jsonObj['data']['url']) && strpos($jsonObj['data']['url'], 'http') === 0) { + $url = $jsonObj['data']['url']; + } + + // 匹配第三方大站开启解析 + if (preg_match('/(?:www\.iqiyi|v\.qq|v\.youku|www\.mgtv|www\.bilibili)\.com/', $url)) { + $jx = 1; + } + + return [ + 'jx' => $jx, + 'parse' => 0, + 'url' => $url, + 'header' => [ + 'User-Agent' => $this->UA, + 'Referer' => 'https://docs.qq.com/' + ] + ]; + } + + private function arr2vods($arr) { + $videos = []; + foreach ($arr as $i) { + // 修复符号错误 + $remarks = ($i['vod_serial'] == '1') + ? $i['vod_serial'] . '集' + : '评分' . ($i['vod_score'] ?? $i['vod_douban_score'] ?? '0'); + + $videos[] = [ + 'vod_id' => $i['vod_id'] ?? '', + 'vod_name' => $i['vod_name'] ?? '', + 'vod_pic' => $i['vod_pic'] ?? '', + 'vod_remarks' => $remarks ?? '' + ]; + } + return $videos; + } +} + +// 运行爬虫 +(new Spider())->run(); diff --git "a/spider/php/\345\212\250\346\274\253\345\225\246 \341\265\210\341\266\273[\347\224\273].php" "b/spider/php/\345\212\250\346\274\253\345\225\246 \341\265\210\341\266\273[\347\224\273].php" new file mode 100644 index 00000000..cef44d1c --- /dev/null +++ "b/spider/php/\345\212\250\346\274\253\345\225\246 \341\265\210\341\266\273[\347\224\273].php" @@ -0,0 +1,330 @@ + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", + "Referer" => "https://www.dongman.la/", + "Connection" => "keep-alive" + ]; + } + + private function fetchHtml($url) { + // 忽略 SSL 验证 + $options = [ + 'headers' => $this->getHeader() + ]; + return $this->fetch($url, $options); + } + + public function homeContent($filter) { + $cats = []; + try { + $html = $this->fetchHtml("https://www.dongman.la/"); + if (preg_match('/

(.*?)<\/div>/s', $html, $matches)) { + if (preg_match_all('/]+href=["\']([^"\']+)["\'][^>]*>(.*?)<\/a>/s', $matches[1], $links, PREG_SET_ORDER)) { + foreach ($links as $link) { + $href = $link[1]; + $title = trim($link[2]); + if (strpos($title, "首页") !== false) continue; + + $typeId = trim(str_replace("https://www.dongman.la", "", $href), "/"); + $cats[] = ["type_name" => $title, "type_id" => $typeId]; + } + } + } + } catch (Exception $e) { + // pass + } + + if (empty($cats)) { + $cats = [ + ["type_name" => "连载中", "type_id" => "manhua/list/lianzai"], + ["type_name" => "已完结", "type_id" => "manhua/list/wanjie"], + ["type_name" => "热血", "type_id" => "manhua/list/rexue"], + ["type_name" => "恋爱", "type_id" => "manhua/list/lianai"], + ["type_name" => "冒险", "type_id" => "manhua/list/maoxian"], + ["type_name" => "搞笑", "type_id" => "manhua/list/gaoxiao"] + ]; + } + + return ["class" => $cats, "filters" => []]; + } + + public function homeVideoContent() { + return $this->categoryContent("manhua/list/lianzai", 1, [], []); + } + + public function categoryContent($tid, $pg = 1, $filter = [], $extend = []) { + $tid = trim($tid, '/'); + $url = "https://www.dongman.la/{$tid}/{$pg}.html"; + return $this->getPostListByRegex($url, $pg); + } + + public function searchContent($key, $quick = false, $pg = 1) { + $url = "https://www.dongman.la/manhua/so/{$key}/{$pg}.html"; + return $this->getPostListByRegex($url, $pg); + } + + private function getPostListByRegex($url, $pg) { + try { + $html = $this->fetchHtml($url); + if (!$html) return ["list" => []]; + + $vlist = []; + $listHtml = ""; + + // 提取列表容器 + if (preg_match('/(?:class=["\']cy_list_mh["\']|id=["\']contaner["\'])[^>]*>(.*?)
(.*?)<\/b>/s', $item, $bMatch)) { + $name = trim($bMatch[1]); + } elseif (preg_match('/class=["\']pic["\'][^>]*title=["\']([^"\']+)["\']/', $item, $tMatch)) { + $name = $tMatch[1]; + } elseif (preg_match('/alt=["\']([^"\']+)["\']/', $item, $altMatch)) { + $name = trim($altMatch[1]); + } + + $name = trim(strip_tags($name)); + $name = str_replace(["漫画", "在线观看"], "", $name); + if (!$name) continue; + + // 提取图片 + $pic = ""; + if (preg_match('/(?:data-src|src)=["\']([^"\']+)["\']/', $item, $imgMatch)) { + $pic = $imgMatch[1]; + if (strpos($pic, "//") === 0) $pic = "https:" . $pic; + } + + // 提取备注 + $remark = ""; + if (preg_match('/]*>(.*?)<\/p>/s', $item, $pMatch)) { + // 确保不是 title 里的部分 + $tempItem = explode($pMatch[0], $item)[0]; + if (strpos($tempItem, 'title') === false) { + $remark = trim(strip_tags($pMatch[1])); + } + } + + if (!$remark && preg_match('/class=["\']tt["\'][^>]*>(.*?)<\/span>/', $item, $ttMatch)) { + $remark = trim($ttMatch[1]); + } + + $vlist[] = [ + 'vod_id' => $href, + 'vod_name' => $name, + 'vod_pic' => $pic, + 'vod_remarks' => $remark + ]; + } + } + + return ["list" => $vlist, "page" => $pg, "pagecount" => 9999, "limit" => 30, "total" => 999999]; + } catch (Exception $e) { + return ["list" => []]; + } + } + + public function detailContent($ids) { + $vid = $ids[0]; + $url = (strpos($vid, 'http') === 0) ? $vid : "https://www.dongman.la{$vid}"; + + try { + $html = $this->fetchHtml($url); + + $name = ""; + if (preg_match('/]*>(.*?)<\/h1>/s', $html, $h1Match)) { + $name = trim(strip_tags($h1Match[1])); + } + + if (!$name && preg_match('/(.*?)<\/title>/s', $html, $titleMatch)) { + $parts = explode('-', $titleMatch[1]); + $parts = explode('_', $parts[0]); + $name = trim($parts[0]); + } + + $name = trim(str_replace(["漫画", "在线观看", "免费阅读"], "", $name)) ?: "未知漫画"; + + $cover = ""; + if (preg_match('/<img[^>]*class=["\'](?:detail-info-cover|pic)["\'][^>]*src=["\']([^"\']+)["\']/', $html, $coverMatch) || + preg_match('/<img[^>]*src=["\']([^"\']+)["\'][^>]*class=["\'](?:detail-info-cover|pic)["\']/', $html, $coverMatch)) { + $cover = $coverMatch[1]; + if (strpos($cover, "//") === 0) $cover = "https:" . $cover; + } + + $desc = ""; + if (preg_match('/id="comic-description"[^>]*>(.*?)<\/div>/s', $html, $descMatch)) { + $desc = trim(strip_tags($descMatch[1])); + $desc = str_replace([" ", "详细简介↓", "收起↑"], [" ", "", ""], $desc); + $desc = preg_replace('/\s+/', ' ', $desc); + } + + // 提取章节 + $linksSource = $html; + if (preg_match_all('/<(?:ul|ol)[^>]*class=["\'].*?list.*?["\'][^>]*>(.*?)<\/(?:ul|ol)>/s', $html, $listContainers)) { + $linksSource = implode("", $listContainers[1]); + } + + $chapterList = []; + $uniqueChapters = []; + + if (preg_match_all('/<a[^>]+href=["\']([^"\']+)["\'][^>]*>(.*?)<\/a>/s', $linksSource, $rawLinks, PREG_SET_ORDER)) { + foreach ($rawLinks as $link) { + $href = $link[1]; + $text = $link[2]; + + if (strpos($href, "/chapter/") === false && !preg_match('/\d+\.html/', $href)) continue; + if (strpos($href, "detail") !== false) continue; + + $title = trim(strip_tags($text)); + if (!$title || strpos($title, "在线阅读") !== false || strpos($title, "开始阅读") !== false) continue; + + if (!in_array($href, $uniqueChapters)) { + $uniqueChapters[] = $href; + $chapterList[] = "{$title}\${$href}"; + } + } + } + + $chapterList = array_reverse($chapterList); + $playUrl = implode("#", $chapterList); + + return [ + "list" => [[ + "vod_id" => $vid, + "vod_name" => $name, + "vod_pic" => $cover, + "type_name" => "漫画", + "vod_content" => $desc, + "vod_play_from" => '动漫啦', + "vod_play_url" => $playUrl + ]] + ]; + } catch (Exception $e) { + return ["list" => []]; + } + } + + private function extractImgs($htmlText) { + $found = []; + // RE_PLAY_IMGS + if (preg_match_all('/(?:data-original|data-src|src)=["\']([^"\']+\.(?:jpg|png|jpeg|webp))[^"\']*["\']/i', $htmlText, $matches)) { + foreach ($matches[1] as $src) { + if (preg_match('/(logo|icon|cover|banner|\.gif|loading)/', $src)) continue; + + if (strpos($src, "//") === 0) { + $src = "https:" . $src; + } elseif (strpos($src, "/") === 0) { + $src = "https://www.dongman.la" . $src; + } elseif (strpos($src, "http") !== 0) { + continue; + } + + if (!in_array($src, $found)) { + $found[] = $src; + } + } + } + return $found; + } + + public function playerContent($flag, $id, $vipFlags = []) { + $url = (strpos($id, 'http') === 0) ? $id : "https://www.dongman.la{$id}"; + + $cleanUrl = rtrim(str_replace('.html', '', $url), '/'); + $allUrl = "{$cleanUrl}/all.html"; + + $imgList = []; + + // 1. 尝试 all.html + try { + $html = $this->fetchHtml($allUrl); + if ($html) { + $imgList = $this->extractImgs($html); + } + } catch (Exception $e) { + // pass + } + + // 2. 失败则循环抓取 (限制前40页) + if (empty($imgList)) { + $imageMap = []; + // PHP 串行抓取 + for ($i = 1; $i < 40; $i++) { + $targetUrl = ($i == 1) ? $url : "{$cleanUrl}/{$i}.html"; + try { + $resHtml = $this->fetchHtml($targetUrl); + if ($resHtml) { + $imgs = $this->extractImgs($resHtml); + if (!empty($imgs)) { + $imageMap[$i] = $imgs[0]; + } else { + // 如果某一页抓不到图片,可能就是结束了,或者反爬,这里可以考虑 break + // 但是为了保险起见,Python 是并发抓取所有,这里我们也继续尝试 + } + } else { + // 404 or error likely means end of chapter + break; + } + } catch (Exception $e) { + break; + } + } + + for ($i = 1; $i < 40; $i++) { + if (isset($imageMap[$i])) { + $imgList[] = $imageMap[$i]; + } + } + } + + if (empty($imgList)) { + // webview fallback + return ['parse' => 1, 'url' => $url, 'header' => json_encode($this->getHeader())]; + } + + $novelData = implode("&&", $imgList); + + return [ + "parse" => 0, + "playUrl" => "", + "url" => "pics://{$novelData}", + "header" => "" + ]; + } +} + +(new Spider())->run(); diff --git "a/spider/php/\345\214\205\345\255\220\346\274\253\347\224\273 \341\265\210\341\266\273[\347\224\273].php" "b/spider/php/\345\214\205\345\255\220\346\274\253\347\224\273 \341\265\210\341\266\273[\347\224\273].php" new file mode 100644 index 00000000..e5ef5f18 --- /dev/null +++ "b/spider/php/\345\214\205\345\255\220\346\274\253\347\224\273 \341\265\210\341\266\273[\347\224\273].php" @@ -0,0 +1,312 @@ +<?php +require_once __DIR__ . '/lib/spider.php'; + +class Spider extends BaseSpider { + + public function getName() { + return "包子漫画"; + } + + public function init($extend = "") { + // pass + } + + public function isVideoFormat($url) { + return false; + } + + public function manualVideoCheck() { + return false; + } + + private function getHeader() { + return [ + "User-Agent" => "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", + "Referer" => "https://cn.bzmanga.com/" + ]; + } + + public function homeContent($filter) { + $classes = [ + ["type_name" => "最新上架", "type_id" => "new"], + ["type_name" => "全部漫画", "type_id" => "all"], + ["type_name" => "地区", "type_id" => "region"], + ["type_name" => "进度", "type_id" => "status"], + ["type_name" => "题材", "type_id" => "type"] + ]; + + $filters = []; + $filters['region'] = [["key" => "val", "name" => "地区", "value" => [["n" => "国漫", "v" => "cn"],["n" => "日本", "v" => "jp"],["n" => "欧美", "v" => "en"]]]]; + $filters['status'] = [["key" => "val", "name" => "进度", "value" => [["n" => "连载中", "v" => "serial"],["n" => "已完结", "v" => "pub"]]]]; + + $types = [ + ["n" => "都市", "v" => "dushi"], ["n" => "冒险", "v" => "mouxian"], + ["n" => "热血", "v" => "rexie"], ["n" => "爱情", "v" => "aiqing"], + ["n" => "恋爱", "v" => "lianai"], ["n" => "耽美", "v" => "danmei"], + ["n" => "武侠", "v" => "wuxia"], ["n" => "格斗", "v" => "gedou"], + ["n" => "科幻", "v" => "kehuan"], ["n" => "魔幻", "v" => "mohuan"], + ["n" => "侦探", "v" => "zhentan"], ["n" => "推理", "v" => "tuili"], + ["n" => "玄幻", "v" => "xuanhuan"], ["n" => "日常", "v" => "richang"], + ["n" => "生活", "v" => "shenghuo"], ["n" => "搞笑", "v" => "gaoxiao"], + ["n" => "校园", "v" => "xiaoyuan"], ["n" => "奇幻", "v" => "qihuan"] + ]; + $filters['type'] = [["key" => "val", "name" => "类型", "value" => $types]]; + + return ["class" => $classes, "filters" => $filters]; + } + + public function homeVideoContent() { + return $this->categoryContent("new", 1, [], []); + } + + public function categoryContent($tid, $pg = 1, $filter = [], $extend = []) { + if ($tid == "new") { + $url = ($pg == 1) ? "https://cn.bzmanga.com/list/new/" : "https://cn.bzmanga.com/list/new/?page={$pg}"; + } elseif ($tid == "all") { + $url = "https://cn.bzmanga.com/classify?page={$pg}"; + } else { + $val = $extend['val'] ?? ''; + if (!$val) { + if ($tid == "region") $val = "cn"; + elseif ($tid == "status") $val = "serial"; + elseif ($tid == "type") $val = "dushi"; + } + + $paramKey = $tid; + if ($tid == "status") $paramKey = "state"; + + $url = "https://cn.bzmanga.com/classify?{$paramKey}={$val}&page={$pg}"; + } + + try { + $html = $this->fetch($url, ['headers' => $this->getHeader()]); + $items = $this->pdfa($html, '.comics-card'); + + $videos = []; + foreach ($items as $item) { + $vid = $this->pd($item, 'a.comics-card__poster&&href'); + if (!$vid) continue; + + $cover = $this->pd($item, 'amp-img&&src'); + if (strpos($cover, ".w=") !== false) { + $cover = explode('.w=', $cover)[0]; + } + + $name = $this->pd($item, '.comics-card__title&&Text'); + + $videos[] = [ + "vod_id" => $vid, + "vod_name" => $name, + "vod_pic" => $cover, + "vod_remarks" => "" + ]; + } + + return [ + "list" => $videos, + "page" => $pg, + "pagecount" => 9999, + "limit" => 36, + "total" => 999999 + ]; + } catch (Exception $e) { + return ["list" => []]; + } + } + + public function detailContent($ids) { + $vid = $ids[0]; + $url = (strpos($vid, 'http') === 0) ? $vid : "https://cn.bzmanga.com{$vid}"; + + try { + $html = $this->fetch($url, ['headers' => $this->getHeader()]); + + $name = $this->pd($html, '.comics-detail__title&&Text') ?: "未知"; + $author = $this->pd($html, '.comics-detail__author&&Text'); + $desc = $this->pd($html, '.comics-detail__desc&&Text'); + + $cover = $this->pd($html, 'amp-img&&src'); + if (strpos($cover, ".w=") !== false) { + $cover = explode('.w=', $cover)[0]; + } + + $chapterItems = $this->pdfa($html, '.comics-chapters__item'); + + $rawUrlList = []; + foreach ($chapterItems as $item) { + $aTag = $this->pd($item, 'a', true); // get element + if (!$aTag && strpos($item, '<a') === 0) { // simple check if item itself is a tag + // HtmlParser doesn't fully support item itself as root sometimes, depend on implementation + // Let's assume pdfa returns inner HTML or node. + // BaseSpider pdfa returns array of strings (html fragments) usually. + // So we can re-parse item + } + + $chapterName = $this->pd($item, 'a&&Text'); + if (!$chapterName) $chapterName = $this->pd($item, 'Text'); // fallback if item is 'a' + + $rawHref = $this->pd($item, 'a&&href'); + if (!$rawHref) $rawHref = $this->pd($item, 'href'); + + if (!$chapterName || !$rawHref) continue; + + $realChapterUrl = ""; + + if (preg_match('/comic_id=(\d+).*chapter_slot=(\d+)/', $rawHref, $matches)) { + $cId = $matches[1]; + $cSlot = $matches[2]; + $realChapterUrl = "https://cn.dzmanga.com/comic/chapter/{$cId}/0_{$cSlot}.html"; + } else { + if (strpos($rawHref, "/") === 0) { + $realChapterUrl = "https://cn.dzmanga.com{$rawHref}"; + } elseif (strpos($rawHref, "http") !== false) { + $realChapterUrl = str_replace("cn.bzmanga.com", "cn.dzmanga.com", $rawHref); + } else { + $realChapterUrl = "https://cn.dzmanga.com/{$rawHref}"; + } + } + + $rawUrlList[] = "{$chapterName}\${$realChapterUrl}"; + } + + $descList = $rawUrlList; + $ascList = array_reverse($rawUrlList); + + $strDesc = implode("#", $descList); + $strAsc = implode("#", $ascList); + + return [ + "list" => [[ + "vod_id" => $vid, + "vod_name" => $name, + "vod_pic" => $cover, + "type_name" => "漫画", + "vod_year" => "", + "vod_area" => "", + "vod_remarks" => $author, + "vod_actor" => "", + "vod_director" => "", + "vod_content" => $desc, + "vod_play_from" => '正序$$$倒序', + "vod_play_url" => "{$strAsc}$$$" . $strDesc + ]] + ]; + } catch (Exception $e) { + return ["list" => []]; + } + } + + public function searchContent($key, $quick = false, $pg = 1) { + $url = "https://cn.bzmanga.com/search?q={$key}"; + try { + $html = $this->fetch($url, ['headers' => $this->getHeader()]); + $items = $this->pdfa($html, '.comics-card'); + + $videos = []; + foreach ($items as $item) { + $vid = $this->pd($item, 'a.comics-card__poster&&href'); + if (!$vid) continue; + + $cover = $this->pd($item, 'amp-img&&src'); + if (strpos($cover, ".w=") !== false) { + $cover = explode('.w=', $cover)[0]; + } + + $name = $this->pd($item, '.comics-card__title&&Text'); + + $videos[] = [ + "vod_id" => $vid, + "vod_name" => $name, + "vod_pic" => $cover, + "vod_remarks" => "" + ]; + } + return ['list' => $videos]; + } catch (Exception $e) { + return ['list' => []]; + } + } + + public function playerContent($flag, $id, $vipFlags = []) { + $url = $id; + $headers = $this->getHeader(); + $headers['Referer'] = $url; + + try { + $html = $this->fetch($url, ['headers' => $headers]); + + $imgList = []; + + // 策略A:DOM解析 + $container = $this->pd($html, '.comic-contain', true); + if (!$container) { + // simple body check, or just parse whole html + // HtmlParser usually handles whole html if no selector matched for subset + } + + $imgs = []; + if ($container) { + // If we had a specific object for container, we'd use it. + // But base spider pd/pdfa usually works on string. + // So let's just search in html + } + + // Use regex for specific container if possible, but here we can just try global selector on html + // but restricted by container class if we could. + // Simplified: Global search with selector + $imgs = $this->pdfa($html, '.comic-contain amp-img'); + if (empty($imgs)) { + $imgs = $this->pdfa($html, '.comic-contain img'); + } + // If still empty, try body (global) + if (empty($imgs)) { + $imgs = $this->pdfa($html, 'amp-img'); + if (empty($imgs)) $imgs = $this->pdfa($html, 'img'); + } + + foreach ($imgs as $img) { + $src = $this->pd($img, 'src'); + if (!$src) $src = $this->pd($img, 'data-src'); + + if ($src) { + if (strpos($src, "next_chapter") !== false || strpos($src, "prev_chapter") !== false || strpos($src, "icon") !== false || strpos($src, "logo") !== false) { + continue; + } + if (strpos($src, "//") === 0) { + $src = "https:" . $src; + } + $imgList[] = $src; + } + } + + // 策略B:暴力正则 + if (count($imgList) < 2) { + if (preg_match_all('/(https?:\/\/[^"\'\s]+static[^"\'\s]+\.(?:jpg|png|webp|jpeg)(?:\?[^"\'\s]*)?)/', $html, $matches)) { + foreach ($matches[1] as $m) { + if (!in_array($m, $imgList)) { + if (strpos($m, "cover") !== false) continue; + if (strpos($m, "icon") !== false) continue; + if (strpos($m, "logo") !== false) continue; + if (strpos($m, "bg") !== false) continue; + $imgList[] = $m; + } + } + } + } + + $uniqueImgs = array_unique($imgList); + $novelData = implode("&&", $uniqueImgs); + + return [ + "parse" => 0, + "playUrl" => "", + "url" => "pics://{$novelData}", + "header" => "" + ]; + } catch (Exception $e) { + return ["parse" => 0, "url" => "", "header" => ""]; + } + } +} + +(new Spider())->run(); diff --git "a/spider/php/\345\216\273\350\257\273\344\271\246 \341\265\210\341\266\273[\344\271\246].php" "b/spider/php/\345\216\273\350\257\273\344\271\246 \341\265\210\341\266\273[\344\271\246].php" new file mode 100644 index 00000000..b580972c --- /dev/null +++ "b/spider/php/\345\216\273\350\257\273\344\271\246 \341\265\210\341\266\273[\344\271\246].php" @@ -0,0 +1,273 @@ +<?php +require_once __DIR__ . '/lib/spider.php'; + +class Spider extends BaseSpider { + private const AES_KEY = '242ccb8230d709e1'; + private const SIGN_KEY = 'd3dGiJc651gSQ8w1'; + private const APP_ID = 'com.kmxs.reader'; + + private $baseHeaders = [ + "app-version" => "51110", + "platform" => "android", + "reg" => "0", + "AUTHORIZATION" => "", + "application-id" => self::APP_ID, + "net-env" => "1", + "channel" => "unknown", + "qm-params" => "" + ]; + + public function getName() { + return "去读书"; + } + + public function init($extend = "") { + // pass + } + + public function isVideoFormat($url) { + return false; + } + + public function manualVideoCheck() { + // pass + } + + private function getSign($params) { + ksort($params); + $signStr = ""; + foreach ($params as $k => $v) { + $signStr .= "{$k}={$v}"; + } + $signStr .= self::SIGN_KEY; + return md5($signStr); + } + + private function getHeaders() { + $headers = $this->baseHeaders; + $headers['sign'] = $this->getSign($headers); + $headers['User-Agent'] = 'okhttp/3.12.1'; + return $headers; + } + + private function decryptContent($base64Content) { + try { + $encryptedBytes = base64_decode($base64Content); + if (strlen($encryptedBytes) < 16) { + return "数据长度不足"; + } + $iv = substr($encryptedBytes, 0, 16); + $ciphertext = substr($encryptedBytes, 16); + + $decrypted = openssl_decrypt( + $ciphertext, + 'AES-128-CBC', + self::AES_KEY, + OPENSSL_RAW_DATA, + $iv + ); + + if ($decrypted === false) { + return "解密失败"; + } + return trim($decrypted); + } catch (Exception $e) { + return "解密错误: " . $e->getMessage(); + } + } + + private function getApiUrl($path, $params, $domainType = "bc") { + $params['sign'] = $this->getSign($params); + $baseUrl = ($domainType == "bc") ? "https://api-bc.wtzw.com" : "https://api-ks.wtzw.com"; + if (strpos($path, "search") !== false) { + $baseUrl = "https://api-bc.wtzw.com"; + } + + $queryString = http_build_query($params); + return ["{$baseUrl}{$path}?{$queryString}", $params]; + } + + public function homeContent($filter) { + $cats = [ + ["n" => "玄幻奇幻", "v" => "1|202"], ["n" => "都市人生", "v" => "1|203"], ["n" => "武侠仙侠", "v" => "1|205"], + ["n" => "历史军事", "v" => "1|56"], ["n" => "科幻末世", "v" => "1|64"], ["n" => "游戏竞技", "v" => "1|75"], + ["n" => "现代言情", "v" => "2|1"], ["n" => "古代言情", "v" => "2|2"], ["n" => "幻想言情", "v" => "2|4"], + ["n" => "婚恋情感", "v" => "2|6"], ["n" => "悬疑推理", "v" => "3|262"] + ]; + + $classes = []; + foreach ($cats as $cat) { + $classes[] = ["type_name" => $cat['n'], "type_id" => $cat['v']]; + } + return ['class' => $classes, 'filters' => []]; + } + + public function homeVideoContent() { + return ['list' => []]; + } + + public function categoryContent($tid, $pg = 1, $filter = [], $extend = []) { + $parts = explode("|", $tid); + $gender = $parts[0] ?? "1"; + $catId = $parts[1] ?? "202"; + + $path = "/api/v4/category/get-list"; + $params = ['gender' => $gender, 'category_id' => $catId, 'need_filters' => '1', 'page' => $pg, 'need_category' => '1']; + $headers = $this->getHeaders(); + list($url, $signedParams) = $this->getApiUrl($path, $params, "bc"); + + try { + $j = $this->fetchJson($url, ['headers' => $headers]); + $videos = []; + $bookList = []; + + if (isset($j['data']['books'])) { + $bookList = $j['data']['books']; + } elseif (isset($j['books'])) { + $bookList = $j['books']; + } + + foreach ($bookList as $book) { + $videos[] = [ + "vod_id" => (string)$book['id'], + "vod_name" => $book['title'], + "vod_pic" => $book['image_link'], + "vod_remarks" => $book['author'] + ]; + } + return ['list' => $videos, 'page' => $pg, 'pagecount' => 999, 'limit' => 20, 'total' => 9999]; + } catch (Exception $e) { + return ['list' => []]; + } + } + + public function detailContent($ids) { + $bid = $ids[0]; + $headers = $this->getHeaders(); + + $detailParams = ['id' => $bid, 'imei_ip' => '2937357107', 'teeny_mode' => '0']; + list($detailUrl, $detailSignedParams) = $this->getApiUrl("/api/v4/book/detail", $detailParams, "bc"); + + $vod = ["vod_id" => $bid, "vod_name" => "获取中...", "vod_play_from" => "去读书"]; + + try { + $j = $this->fetchJson($detailUrl, ['headers' => $headers]); + if (isset($j['data']['book'])) { + $bookInfo = $j['data']['book']; + $vod["vod_name"] = $bookInfo['title']; + $vod["vod_pic"] = $bookInfo['image_link']; + $vod["type_name"] = $bookInfo['category_name'] ?? ''; + $vod["vod_remarks"] = ($bookInfo['words_num'] ?? '') . "字"; + $vod["vod_actor"] = $bookInfo['author']; + $vod["vod_content"] = $bookInfo['intro']; + } + + // 获取目录 + $chapterParams = ['id' => $bid]; + list($chapterUrl, $chapterSignedParams) = $this->getApiUrl("/api/v1/chapter/chapter-list", $chapterParams, "ks"); + + $jc = $this->fetchJson($chapterUrl, ['headers' => $headers]); + + $chapterList = []; + $lists = []; + if (isset($jc['data']['chapter_lists'])) { + $lists = $jc['data']['chapter_lists']; + } + + foreach ($lists as $item) { + $cid = (string)$item['id']; + $cname = str_replace(["@@", "$"], ["-", ""], (string)$item['title']); + $urlCode = "{$bid}@@{$cid}@@{$cname}"; + $chapterList[] = "{$cname}\${$urlCode}"; + } + + $vod['vod_play_url'] = implode("#", $chapterList); + return ["list" => [$vod]]; + } catch (Exception $e) { + $vod["vod_content"] = "Error: " . $e->getMessage(); + return ["list" => [$vod]]; + } + } + + public function searchContent($key, $quick = false, $pg = 1) { + $path = "/api/v5/search/words"; + $params = ['gender' => '3', 'imei_ip' => '2937357107', 'page' => $pg, 'wd' => $key]; + $headers = $this->getHeaders(); + list($url, $signedParams) = $this->getApiUrl($path, $params, "bc"); + + try { + $j = $this->fetchJson($url, ['headers' => $headers]); + $videos = []; + if (isset($j['data']['books'])) { + foreach ($j['data']['books'] as $book) { + $videos[] = [ + "vod_id" => (string)$book['id'], + "vod_name" => $book['original_title'], + "vod_pic" => $book['image_link'], + "vod_remarks" => $book['original_author'] + ]; + } + } + return ['list' => $videos, 'page' => $pg]; + } catch (Exception $e) { + return ['list' => [], 'page' => $pg]; + } + } + + public function playerContent($flag, $id, $vipFlags = []) { + try { + $parts = explode("@@", $id); + $bid = $parts[0]; + $cid = $parts[1]; + $title = isset($parts[2]) ? $parts[2] : ""; + + $params = ['id' => $bid, 'chapterId' => $cid]; + $headers = $this->getHeaders(); + list($url, $signedParams) = $this->getApiUrl("/api/v1/chapter/content", $params, "ks"); + + $j = $this->fetchJson($url, ['headers' => $headers]); + + $content = ""; + if (isset($j['data']['content'])) { + if (empty($title) && isset($j['data']['title'])) { + $title = $j['data']['title']; + } + $content = $this->decryptContent($j['data']['content']); + } else { + $content = "加载失败: " . ($j['msg'] ?? '未知错误'); + } + + if (empty($title)) { + $title = "章节正文"; + } + + $resultData = [ + 'title' => $title, + 'content' => $content + ]; + + $ret = json_encode($resultData, JSON_UNESCAPED_UNICODE); + $finalUrl = "novel://{$ret}"; + + return [ + "parse" => 0, + "playUrl" => "", + "url" => $finalUrl, + "header" => "" + ]; + } catch (Exception $e) { + $errData = [ + 'title' => "错误", + 'content' => "发生异常: " . $e->getMessage() + ]; + return [ + "parse" => 0, + "playUrl" => "", + "url" => "novel://" . json_encode($errData, JSON_UNESCAPED_UNICODE), + "header" => "" + ]; + } + } +} + +(new Spider())->run(); diff --git "a/spider/php/\345\223\207\345\223\207\345\275\261\350\247\206 \341\265\210\341\266\273.php" "b/spider/php/\345\223\207\345\223\207\345\275\261\350\247\206 \341\265\210\341\266\273.php" new file mode 100644 index 00000000..aef5b5e2 --- /dev/null +++ "b/spider/php/\345\223\207\345\223\207\345\275\261\350\247\206 \341\265\210\341\266\273.php" @@ -0,0 +1,161 @@ +<?php +require_once __DIR__ . '/lib/spider.php'; + +// ================= 核心加解密类 ================= +class WawaCrypto { + public static function decrypt($encrypted_data) { + $key = base64_decode('Crm4FXWkk5JItpYirFDpqg=='); // + $data = hex2bin(base64_decode($encrypted_data)); // + return openssl_decrypt($data, 'AES-128-ECB', $key, OPENSSL_RAW_DATA); + } + + public static function sign($message, $privateKey) { + $key = "-----BEGIN PRIVATE KEY-----\n" . wordwrap($privateKey, 64, "\n", true) . "\n-----END PRIVATE KEY-----"; + $res = openssl_get_privatekey($key); + openssl_sign($message, $signature, $res, OPENSSL_ALGO_SHA256); // 使用 SHA256 签名 + return base64_encode($signature); + } + + public static function uuid() { + return sprintf('%04x%04x%04x%04x%04x%04x%04x%04x', + mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff), + mt_rand(0, 0x0fff) | 0x4000, mt_rand(0, 0x3fff) | 0x8000, + mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff) + ); + } +} + +class Spider extends BaseSpider { + private $HOST; + private $APP_KEY; + private $RSA_KEY; + private $CONF; + + public function init($extend = '') { + $this->initConf(); + } + + private function initConf() { + $uid = WawaCrypto::uuid(); + $t = (string)(time() * 1000); + $sign = md5("appKey=3bbf7348cf314874883a18d6b6fcf67a&uid=$uid&time=$t"); + + $url = 'https://gitee.com/api/v5/repos/aycapp/openapi/contents/wawaconf.txt?access_token=74d5879931b9774be10dee3d8c51008e'; + $res = json_decode($this->fetch($url, [], ["User-Agent: okhttp/4.9.3", "uid: $uid", "time: $t", "sign: $sign"]), true); + + $this->CONF = json_decode(WawaCrypto::decrypt($res['content']), true); + $this->HOST = $this->CONF['baseUrl']; + $this->APP_KEY = $this->CONF['appKey']; + $this->RSA_KEY = $this->CONF['appSecret']; + } + + private function getWawaHeaders() { + $uid = WawaCrypto::uuid(); + $t = (string)(time() * 1000); + $sign = WawaCrypto::sign("appKey={$this->APP_KEY}&time=$t&uid=$uid", $this->RSA_KEY); + return [ + 'User-Agent: okhttp/4.9.3', + "uid: $uid", + "time: $t", + "appKey: {$this->APP_KEY}", + "sign: $sign" + ]; + } + + public function homeContent($filter) { + $typeData = json_decode($this->fetch("{$this->HOST}/api.php/zjv6.vod/types", [], $this->getWawaHeaders()), true); + $classes = []; + $filters = []; + $dy = ["class" => "类型", "area" => "地区", "lang" => "语言", "year" => "年份", "letter" => "字母", "by" => "排序"]; + $sl = ['按更新' => 'time', '按播放' => 'hits', '按评分' => 'score', '按收藏' => 'store_num']; + + if (isset($typeData['data']['list'])) { + foreach ($typeData['data']['list'] as $item) { + $classes[] = ['type_id' => $item['type_id'], 'type_name' => $item['type_name']]; + $tid = (string)$item['type_id']; + $filters[$tid] = []; + $item['type_extend']['by'] = '按更新,按播放,按评分,按收藏'; // 强制注入排序 + + foreach ($dy as $key => $name) { + if (!empty($item['type_extend'][$key])) { + $values = explode(',', $item['type_extend'][$key]); + $value_array = []; + foreach ($values as $v) { + if (empty($v)) continue; + $value_array[] = ["n" => $v, "v" => ($key == "by" ? ($sl[$v] ?? $v) : $v)]; + } + $filters[$tid][] = ["key" => $key, "name" => $name, "value" => $value_array]; + } + } + } + } + + $homeList = json_decode($this->fetch("{$this->HOST}/api.php/zjv6.vod/vodPhbAll", [], $this->getWawaHeaders()), true); + $list = $homeList['data']['list'][0]['vod_list'] ?: []; + + return [ + 'class' => $classes, + 'filters' => $filters, + 'list' => $list + ]; + } + + public function categoryContent($tid, $pg = 1, $filter = [], $extend = []) { + $query = http_build_query([ + 'type' => $tid, 'page' => $pg, 'limit' => '12', + 'class' => $extend['class'] ?? '', 'area' => $extend['area'] ?? '', + 'year' => $extend['year'] ?? '', 'by' => $extend['by'] ?? '' + ]); + $res = json_decode($this->fetch("{$this->HOST}/api.php/zjv6.vod?$query", [], $this->getWawaHeaders()), true); + + $list = $res['data']['list'] ?: []; + // 哇哇影视未返回总数,估算分页 + return $this->pageResult($list, $pg, 0, 12); + } + + public function detailContent($ids) { + $id = is_array($ids) ? $ids[0] : $ids; + $res = json_decode($this->fetch("{$this->HOST}/api.php/zjv6.vod/detail?vod_id=$id&rel_limit=10", [], $this->getWawaHeaders()), true); + $item = $res['data']; + $playFrom = []; $playUrls = []; + + if (isset($item['vod_play_list'])) { + foreach ($item['vod_play_list'] as $list) { + $playFrom[] = $list['player_info']['show']; + $urls = []; + foreach ($list['urls'] as $u) { + $u['parse'] = $list['player_info']['parse2']; + $urls[] = $u['name'] . '$' . base64_encode(json_encode($u)); + } + $playUrls[] = implode('#', $urls); + } + } + + return ['list' => [[ + 'vod_id' => $item['vod_id'], + 'vod_name' => $item['vod_name'], + 'vod_pic' => $item['vod_pic'], + 'vod_remarks' => $item['vod_remarks'], + 'vod_content' => $item['vod_content'] ?? '', + 'vod_play_from' => implode('$$$', $playFrom), + 'vod_play_url' => implode('$$$', $playUrls) + ]]]; + } + + public function searchContent($key, $quick = false, $pg = 1) { + $res = json_decode($this->fetch("{$this->HOST}/api.php/zjv6.vod?page=$pg&limit=20&wd=".urlencode($key), [], $this->getWawaHeaders()), true); + $list = $res['data']['list'] ?: []; + return $this->pageResult($list, $pg, 0, 20); + } + + public function playerContent($flag, $id, $vipFlags = []) { + $playData = json_decode(base64_decode($id), true); + return [ + 'parse' => 1, + 'url' => $playData['url'], + 'header' => ['User-Agent' => 'dart:io'] + ]; + } +} + +(new Spider())->run(); diff --git "a/spider/php/\345\227\267\345\221\234\345\212\250\346\274\253 \341\265\210\341\266\273.php" "b/spider/php/\345\227\267\345\221\234\345\212\250\346\274\253 \341\265\210\341\266\273.php" new file mode 100644 index 00000000..f846183a --- /dev/null +++ "b/spider/php/\345\227\267\345\221\234\345\212\250\346\274\253 \341\265\210\341\266\273.php" @@ -0,0 +1,256 @@ +<?php +require_once __DIR__ . '/lib/spider.php'; + +class Spider extends BaseSpider { + private $HOST = 'https://www.aowu.tv'; + // 使用手机 UA 防止拦截 + private $UA = 'Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.91 Mobile Safari/537.36'; + + protected function getHeaders() { + return [ + 'User-Agent: ' . $this->UA, + 'Referer: ' . $this->HOST + ]; + } + + private function fixUrl($url) { + if (empty($url)) return ''; + if (strpos($url, '//') === 0) return 'https:' . $url; + if (strpos($url, '/') === 0) return $this->HOST . $url; + if (strpos($url, 'http') !== 0) return $this->HOST . '/' . $url; + return $url; + } + + // 解析 HTML 列表 (首页/搜索用) + private function parseHtmlList($html, $isSearch = false) { + $videos = []; + if (!$html) return $videos; + + $pattern = $isSearch + ? '/<div class="search-list[^"]*">(.*?)<div class="right">/is' + : '/<div class="public-list-box[^"]*">(.*?)<\/div>\s*<\/div>/is'; + + preg_match_all($pattern, $html, $matches); + + if (!empty($matches[1])) { + foreach ($matches[1] as $itemHtml) { + if (!preg_match('/href="([^"]+)"/', $itemHtml, $m)) continue; + $href = $m[1]; + + $title = ''; + if (preg_match('/alt="([^"]+)"/', $itemHtml, $m)) $title = $m[1]; + elseif (preg_match('/title="([^"]+)"/', $itemHtml, $m)) $title = $m[1]; + + $pic = ''; + if (preg_match('/data-src="([^"]+)"/', $itemHtml, $m)) $pic = $m[1]; + elseif (preg_match('/src="([^"]+)"/', $itemHtml, $m)) $pic = $m[1]; + + $remarks = ''; + if (preg_match('/<span class="public-list-prb[^"]*">([^<]+)<\/span>/', $itemHtml, $m)) { + $remarks = strip_tags($m[1]); + } elseif (preg_match('/<span class="public-prt"[^>]*>([^<]+)<\/span>/', $itemHtml, $m)) { + $remarks = strip_tags($m[1]); + } + + if ($title) { + $videos[] = [ + 'vod_id' => $this->fixUrl($href), + 'vod_name' => trim($title), + 'vod_pic' => $this->fixUrl($pic), + 'vod_remarks' => trim($remarks) + ]; + } + } + } + return $videos; + } + + public function homeContent($filter) { + // 首页 (精选 + 筛选配置) + $html = $this->fetch($this->HOST . '/', [], $this->getHeaders()); + $list = $this->parseHtmlList($html, false); + $list = array_slice($list, 0, 20); + + $classes = [ + ['type_id' => '20', 'type_name' => '🔥 当季新番'], + ['type_id' => '21', 'type_name' => '🎬 番剧'], + ['type_id' => '22', 'type_name' => '🎥 剧场'] + ]; + + // 筛选配置 + $filters = $this->getFilters(); + + return [ + 'class' => $classes, + 'filters' => $filters, + 'list' => $list + ]; + } + + // 筛选配置 (参照 JS 源码配置) + private function getFilters() { + $classes = ['搞笑','恋爱','校园','后宫','治愈','日常','原创','战斗','百合','BL','卖肉','漫画改','游戏改','异世界','泡面番','轻小说改','OVA','OAD','京阿尼','芳文社','A-1Pictures','CloverWorks','J.C.STAFF','动画工房','SUNRISE','Production.I.G','MADHouse','BONES','P.A.WORKS','SHAFT','MAPPA','ufotable','TRIGGER','WITSTUDIO']; + + $years = []; + for ($i = 2026; $i >= 1990; $i--) $years[] = (string)$i; + + // 构建筛选结构 + $classValues = [['n' => '全部', 'v' => '']]; + foreach ($classes as $c) $classValues[] = ['n' => $c, 'v' => $c]; + + $yearValues = [['n' => '全部', 'v' => '']]; + foreach ($years as $y) $yearValues[] = ['n' => $y, 'v' => $y]; + + $sortValues = [ + ['n' => '按最新', 'v' => 'time'], + ['n' => '按最热', 'v' => 'hits'], + ['n' => '按评分', 'v' => 'score'] + ]; + + $rules = [ + ['key' => 'class', 'name' => '剧情', 'value' => $classValues], + ['key' => 'year', 'name' => '年份', 'value' => $yearValues], + ['key' => 'by', 'name' => '排序', 'value' => $sortValues] + ]; + + // 应用到所有分类 + return [ + '20' => $rules, + '21' => $rules, + '22' => $rules + ]; + } + + public function categoryContent($tid, $pg = 1, $filter = [], $extend = []) { + $apiUrl = $this->HOST . '/index.php/ds_api/vod'; + + // 构建 POST 数据 + $postParams = [ + 'type' => $tid, + 'class' => $extend['class'] ?? '', + 'year' => $extend['year'] ?? '', + 'by' => $extend['by'] ?? 'time', // 默认按最新 + 'page' => $pg + ]; + + // 发送 POST 请求 (必须带上 content-type) + $headers = array_merge($this->getHeaders(), [ + 'Content-Type: application/x-www-form-urlencoded; charset=utf-8' + ]); + + $jsonStr = $this->fetch($apiUrl, [ + CURLOPT_POST => 1, + CURLOPT_POSTFIELDS => http_build_query($postParams), + CURLOPT_HTTPHEADER => $headers + ]); + + $jsonObj = json_decode($jsonStr, true); + $list = []; + + if ($jsonObj && isset($jsonObj['list']) && is_array($jsonObj['list'])) { + foreach ($jsonObj['list'] as $it) { + $list[] = [ + 'vod_id' => $this->fixUrl($it['url']), + 'vod_name' => $it['vod_name'], + 'vod_pic' => $this->fixUrl($it['vod_pic']), + 'vod_remarks' => $it['vod_remarks'] + ]; + } + } + + $total = $jsonObj['total'] ?? 0; + $limit = $jsonObj['limit'] ?? 30; + + return $this->pageResult($list, $pg, $total, $limit); + } + + public function detailContent($ids) { + $id = is_array($ids) ? $ids[0] : $ids; + $url = (strpos($id, 'http') === 0) ? $id : $this->fixUrl($id); + $html = $this->fetch($url, [], $this->getHeaders()); + + $vod = [ + 'vod_id' => $id, 'vod_name' => '', 'vod_pic' => '', + 'vod_content' => '', 'vod_play_from' => '', 'vod_play_url' => '' + ]; + + if ($html) { + if (preg_match('/<title>(.*?)<\/title>/', $html, $m)) + $vod['vod_name'] = trim(preg_replace('/\s*-\s*嗷呜动漫.*$/', '', $m[1])); + + if (preg_match('/data-original="([^"]+)"/', $html, $m)) $vod['vod_pic'] = $this->fixUrl($m[1]); + elseif (preg_match('/class="detail-pic"[^>]*src="([^"]+)"/', $html, $m)) $vod['vod_pic'] = $this->fixUrl($m[1]); + + if (preg_match('/class="text cor3"[^>]*>(.*?)<\/div>/is', $html, $m)) + $vod['vod_content'] = trim(strip_tags($m[1])); + + $playFrom = []; + preg_match('/<div class="anthology-tab[^"]*">(.*?)<\/div>/is', $html, $tabHtml); + if (!empty($tabHtml[1])) { + preg_match_all('/<a[^>]*>([^<]+)<\/a>/', $tabHtml[1], $tabNames); + if (!empty($tabNames[1])) { + foreach($tabNames[1] as $idx => $name) { + $name = trim(preg_replace('/ /', '', $name)); + $playFrom[] = $name ?: "线路".($idx+1); + } + } + } + + $playUrls = []; + preg_match_all('/<div class="anthology-list-play[^"]*">(.*?)<\/div>\s*<\/div>/is', $html, $listBoxes); + if (empty($listBoxes[1])) preg_match_all('/<ul class="anthology-list-play[^"]*">(.*?)<\/ul>/is', $html, $listBoxes); + + if (!empty($listBoxes[1])) { + foreach ($listBoxes[1] as $listHtml) { + preg_match_all('/<a[^>]*href="([^"]+)"[^>]*>(.*?)<\/a>/is', $listHtml, $links); + $episodes = []; + if (!empty($links[1])) { + foreach ($links[1] as $k => $href) { + $episodes[] = trim(strip_tags($links[2][$k])) . '$' . $this->fixUrl($href); + } + } + $playUrls[] = implode('#', $episodes); + } + } + + if (empty($playFrom) && !empty($playUrls)) { + for($i=0; $i<count($playUrls); $i++) $playFrom[] = "线路".($i+1); + } + + if (count($playFrom) >= 3) { + array_shift($playFrom); + array_shift($playUrls); + } + + $vod['vod_play_from'] = implode('$$$', $playFrom); + $vod['vod_play_url'] = implode('$$$', $playUrls); + } + + return ['list' => [$vod]]; + } + + public function searchContent($key, $quick = false, $pg = 1) { + $url = $this->HOST . '/search/' . urlencode($key) . '----------' . $pg . '---.html'; + $html = $this->fetch($url, [], $this->getHeaders()); + $list = $this->parseHtmlList($html, true); + + return $this->pageResult($list, $pg, 0, 30); + } + + public function playerContent($flag, $id, $vipFlags = []) { + $url = $id; + if (strpos($url, 'http') === false) $url = $this->fixUrl($url); + + return [ + 'parse' => 1, // 开启嗅探 + 'url' => $url, + 'header' => [ + 'User-Agent' => $this->UA, + 'Referer' => $this->HOST . '/' + ] + ]; + } +} + +// 运行爬虫 +(new Spider())->run(); diff --git "a/spider/php/\345\233\264\350\247\202\347\237\255\345\211\247 \341\265\210\341\266\273.php" "b/spider/php/\345\233\264\350\247\202\347\237\255\345\211\247 \341\265\210\341\266\273.php" new file mode 100644 index 00000000..efb20f98 --- /dev/null +++ "b/spider/php/\345\233\264\350\247\202\347\237\255\345\211\247 \341\265\210\341\266\273.php" @@ -0,0 +1,164 @@ +<?php +require_once __DIR__ . '/lib/spider.php'; + +class Spider extends BaseSpider { + private $HOST = 'https://api.drama.9ddm.com'; + private $UA = 'okhttp/3.12.11'; + + protected function getHeaders() { + return [ + 'User-Agent: ' . $this->UA, + 'Content-Type: application/json;charset=utf-8' + ]; + } + + public function homeContent($filter) { + // 获取分类标签 (对应原 JS class_parse) + $html = $this->fetch($this->HOST . '/drama/home/shortVideoTags', [], $this->getHeaders()); + $data = json_decode($html, true); + + $classes = []; + $filterObj = []; + + if (isset($data['audiences'])) { + foreach ($data['audiences'] as $audience) { + $classes[] = ['type_id' => $audience, 'type_name' => $audience]; + + // 构建筛选 (标签) + $tagValues = [['n' => '全部', 'v' => '']]; + if (isset($data['tags'])) { + foreach ($data['tags'] as $tag) { + $tagValues[] = ['n' => $tag, 'v' => $tag]; + } + } + + $filterObj[$audience] = [ + ['key' => 'tag', 'name' => '标签', 'value' => $tagValues] + ]; + } + } + + return [ + 'class' => $classes, + 'filters' => $filterObj, + 'list' => [] // 首页展示可留空或调用 categoryContent + ]; + } + + public function categoryContent($tid, $pg = 1, $filter = [], $extend = []) { + $apiUrl = $this->HOST . '/drama/home/search'; + + $postData = [ + "audience" => $tid, + "page" => (int)$pg, + "pageSize" => 30, + "searchWord" => "", + "subject" => $extend['tag'] ?? "" + ]; + + $jsonStr = $this->fetch($apiUrl, [ + CURLOPT_POST => 1, + CURLOPT_POSTFIELDS => json_encode($postData), + CURLOPT_HTTPHEADER => $this->getHeaders() + ]); + + $response = json_decode($jsonStr, true); + $list = []; + + if (isset($response['data']) && is_array($response['data'])) { + foreach ($response['data'] as $it) { + $list[] = [ + 'vod_id' => $it['oneId'], + 'vod_name' => $it['title'], + 'vod_pic' => $it['vertPoster'], + 'vod_remarks' => "集数:{$it['episodeCount']} 播放:{$it['viewCount']}", + 'vod_year' => (string)($it['publishDate'] ?? '') + ]; + } + } + + return $this->pageResult($list, $pg, 999, 30); + } + + public function detailContent($ids) { + $id = is_array($ids) ? $ids[0] : $ids; + // 详情接口地址 + $url = $this->HOST . "/drama/home/shortVideoDetail?oneId={$id}&page=1&pageSize=1000"; + + $html = $this->fetch($url, [], $this->getHeaders()); + $response = json_decode($html, true); + $data = $response['data'] ?? []; + + if (empty($data)) return ['list' => []]; + + $first = $data[0]; + $vod = [ + 'vod_id' => $id, + 'vod_name' => $first['title'], + 'vod_pic' => $first['vertPoster'], + 'vod_remarks' => "共" . count($data) . "集", + 'vod_content' => "播放量:{$first['collectionCount']} 评论:{$first['commentCount']} " . ($first['description'] ?? ""), + 'vod_play_from' => '围观短剧' + ]; + + $playUrls = []; + foreach ($data as $episode) { + // 原 JS 逻辑:将整个 playSetting JSON 存入 URL,在 lazy/playContent 中解析 + $playUrls[] = "第{$episode['playOrder']}集$" . $episode['playSetting']; + } + + $vod['vod_play_url'] = implode('#', $playUrls); + + return ['list' => [$vod]]; + } + + public function searchContent($key, $quick = false, $pg = 1) { + $apiUrl = $this->HOST . '/drama/home/search'; + $postData = [ + "audience" => "", + "page" => (int)$pg, + "pageSize" => 30, + "searchWord" => $key, + "subject" => "" + ]; + + $jsonStr = $this->fetch($apiUrl, [ + CURLOPT_POST => 1, + CURLOPT_POSTFIELDS => json_encode($postData), + CURLOPT_HTTPHEADER => $this->getHeaders() + ]); + + $response = json_decode($jsonStr, true); + $list = []; + if (isset($response['data'])) { + foreach ($response['data'] as $it) { + $list[] = [ + 'vod_id' => $it['oneId'], + 'vod_name' => $it['title'], + 'vod_pic' => $it['vertPoster'], + 'vod_remarks' => $it['description'] + ]; + } + } + return $this->pageResult($list, $pg, 0, 30); + } + + public function playerContent($flag, $id, $vipFlags = []) { + // id 此时是 detailContent 传过来的 playSetting JSON 字符串 + $playSetting = json_decode($id, true); + + // 优先级:高清 > 普通 > 流畅 + $videoUrl = $playSetting['high'] ?? $playSetting['normal'] ?? $playSetting['super'] ?? ''; + + return [ + 'parse' => 0, // 短剧通常是直链,无需嗅探 + 'url' => $videoUrl, + 'header' => [ + 'User-Agent' => $this->UA + ] + ]; + } +} + +// 运行爬虫 +(new Spider())->run(); diff --git "a/spider/php/\345\235\232\346\236\234\350\247\206\351\242\221 \341\265\210\341\266\273.php" "b/spider/php/\345\235\232\346\236\234\350\247\206\351\242\221 \341\265\210\341\266\273.php" new file mode 100644 index 00000000..584778e3 --- /dev/null +++ "b/spider/php/\345\235\232\346\236\234\350\247\206\351\242\221 \341\265\210\341\266\273.php" @@ -0,0 +1,200 @@ +<?php +require_once __DIR__ . '/lib/spider.php'; + +class Spider extends BaseSpider { + private $HOST = "http://106.53.107.16"; // 默认起始地址 + private $UA = 'Dart/3.9 (dart:io)'; + + protected function getHeaders() { + return [ + 'User-Agent: ' . $this->UA, + 'Accept-Encoding: gzip', + 'Content-Type: application/json' + ]; + } + + /** + * 初始化:检测有效域名 + */ + private function getValidHost() { + // 如果 extend 传入了不同地址,可以在此处动态修改 $this->HOST + // 此处还原 Python 中的检测逻辑 + $checkUrl = rtrim($this->HOST, '/') . '/success.txt'; + try { + // 简单的存活性检测 + $res = $this->fetch($checkUrl, [CURLOPT_TIMEOUT => 5], $this->getHeaders()); + if ($res) { + return rtrim($this->HOST, '/'); + } + } catch (Exception $e) {} + return rtrim($this->HOST, '/'); + } + + public function homeContent($filter) { + $host = $this->getValidHost(); + $classes = []; + + // 1. 获取常规分类 + $res1 = $this->fetch($host . '/api.php/type/get_list', [], $this->getHeaders()); + $data1 = json_decode($res1, true); + if (isset($data1['info']['rows'])) { + foreach ($data1['info']['rows'] as $row) { + if ($row['type_status'] == 1 && !in_array($row['type_name'], ['漫画', '小说'])) { + $classes[] = ['type_id' => $row['type_id'], 'type_name' => $row['type_name']]; + } + } + } + + // 2. 获取短视频分类 + try { + $res2 = $this->fetch($host . '/addons/getstar/api.index/shortVideoCategory', [], $this->getHeaders()); + $data2 = json_decode($res2, true); + if (isset($data2['data'])) { + foreach ($data2['data'] as $row) { + $classes[] = ['type_id' => $row['id'], 'type_name' => $row['name']]; + } + } + } catch (Exception $e) {} + + return ['class' => $classes]; + } + + public function homeVideoContent() { + $host = $this->getValidHost(); + $url = $host . '/index.php/ajax/data?mid=1&limit=100&page=1&level=7'; + $res = $this->fetch($url, [], $this->getHeaders()); + return json_decode($res, true); + } + + public function categoryContent($tid, $pg = 1, $filter = [], $extend = []) { + $host = $this->getValidHost(); + $year = date("Y"); + $url = "{$host}/index.php/ajax/data?mid=1&limit=20&page={$pg}&tid={$tid}&year={$year}"; + $res = $this->fetch($url, [], $this->getHeaders()); + $json = json_decode($res, true); + $list = $json['list'] ?? []; + $total = $json['total'] ?? 0; + return $this->pageResult($list, $pg, $total, 20); + } + + public function searchContent($key, $quick = false, $pg = 1) { + $host = $this->getValidHost(); + $url = "{$host}/index.php/ajax/data?mid=1&limit=20&page={$pg}&wd=" . urlencode($key); + $res = $this->fetch($url, [], $this->getHeaders()); + $json = json_decode($res, true); + $list = $json['list'] ?? []; + $total = $json['total'] ?? 0; + return $this->pageResult($list, $pg, $total, 20); + } + + public function detailContent($ids) { + $host = $this->getValidHost(); + $id = is_array($ids) ? $ids[0] : $ids; + + // 1. 获取解析配置 (PlayerParse) + $playerConfigs = []; + try { + $pRes = $this->fetch($host . '/addons/getstar/api.index/getPlayerParse', [], $this->getHeaders()); + $pData = json_decode($pRes, true); + if (isset($pData['data']) && is_array($pData['data'])) { + $playerConfigs = $pData['data']; + } + } catch (Exception $e) {} + + // 2. 获取视频详情 + $res = $this->fetch($host . "/api.php/vod/get_detail?vod_id={$id}", [], $this->getHeaders()); + $json = json_decode($res, true); + $data = $json['info'][0]; + + if (!empty($data['vod_play_from']) && !empty($data['vod_play_url'])) { + $froms = explode('$$$', $data['vod_play_from']); + $urls = explode('$$$', $data['vod_play_url']); + + $newFroms = []; + $newUrls = []; + + foreach ($froms as $key => $show) { + $parseUrl = ''; + $isOpen = false; + + // 匹配解析器 + foreach ($playerConfigs as $pConf) { + if ($pConf['code'] == $show) { + $isOpen = true; + $name = $pConf['name'] ?? ''; + if ($name && $name != $show) { + $show = "{$name} ({$show})"; + } + $parseUrl = $pConf['url'] ?? ''; + break; + } + } + + if (!$isOpen) continue; + + $episodeParts = explode('#', $urls[$key]); + $formattedEpisodes = []; + foreach ($episodeParts as $part) { + if (empty($part)) continue; + $temp = explode('$', $part, 2); + $epName = $temp[0]; + $epUrl = $temp[1] ?? ''; + + // 将解析地址附加到 URL 后,供 playContent 使用 + $suffix = $parseUrl ? "@{$parseUrl}" : ""; + $formattedEpisodes[] = "{$epName}\${$epUrl}{$suffix}"; + } + + $newFroms[] = $show; + $newUrls[] = implode('#', $formattedEpisodes); + } + + $data['vod_play_from'] = implode('$$$', $newFroms); + $data['vod_play_url'] = implode('$$$', $newUrls); + } + + return ['list' => [$data]]; + } + + public function playerContent($flag, $id, $vipFlags = []) { + $rawUrl = $id; + $url = ""; + $jx = 0; + + // 处理带 @ 的自定义解析 + if (strpos($id, '@') !== false) { + list($rawUrl, $parse) = explode('@', $id, 2); + $headers = [ + 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36' + ]; + try { + $res = $this->fetch($parse . $rawUrl, [], $headers); + $json = json_decode($res, true); + if (!empty($json['url']) && $json['url'] != $rawUrl) { + $url = $json['url']; + } + } catch (Exception $e) {} + } + + if (empty($url)) { + $url = $rawUrl; + // 匹配大站链接开启嗅探 + if (preg_match('/(?:www\.iqiyi|v\.qq|v\.youku|www\.mgtv|www\.bilibili)\.com/', $rawUrl)) { + $jx = 1; + } + } + + return [ + 'jx' => $jx, + 'parse' => 0, + 'url' => $url, + 'header' => [ + 'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', + 'Connection' => 'Keep-Alive' + ] + ]; + } +} + +// 运行 +(new Spider())->run(); diff --git "a/spider/php/\345\245\207\345\245\207 \341\265\210\341\266\273.php" "b/spider/php/\345\245\207\345\245\207 \341\265\210\341\266\273.php" new file mode 100644 index 00000000..687b1f7f --- /dev/null +++ "b/spider/php/\345\245\207\345\245\207 \341\265\210\341\266\273.php" @@ -0,0 +1,298 @@ +<?php +require_once __DIR__ . '/lib/spider.php'; + +class Spider extends BaseSpider { + private $host = 'https://www.iqiyi.com'; + + protected function getHeaders() { + return [ + 'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', + 'Referer' => 'https://www.iqiyi.com', + 'Accept' => 'application/json, text/plain, */*', + 'Accept-Language' => 'zh-CN,zh;q=0.9,en;q=0.8', + 'Connection' => 'keep-alive' + ]; + } + + public function homeContent($filter) { + $classes = [ + ['type_id' => '1', 'type_name' => '电影'], + ['type_id' => '2', 'type_name' => '电视剧'], + ['type_id' => '6', 'type_name' => '综艺'], + ['type_id' => '4', 'type_name' => '动漫'], + ['type_id' => '3', 'type_name' => '纪录片'], + ['type_id' => '5', 'type_name' => '音乐'], + ['type_id' => '16', 'type_name' => '网络电影'] + ]; + + $filters = [ + '1' => [[ + 'key' => 'year', + 'name' => '年代', + 'value' => [['n' => '全部', 'v' => ''], ['n' => '2025', 'v' => '2025'], ['n' => '2024', 'v' => '2024'], ['n' => '2023', 'v' => '2023']] + ]], + '2' => [[ + 'key' => 'year', + 'name' => '年代', + 'value' => [['n' => '全部', 'v' => ''], ['n' => '2025', 'v' => '2025'], ['n' => '2024', 'v' => '2024'], ['n' => '2023', 'v' => '2023']] + ]] + ]; + + return [ + 'class' => $classes, + 'filters' => $filters + ]; + } + + public function categoryContent($tid, $pg = 1, $filter = [], $extend = []) { + $channelId = $tid; + $dataType = 1; + $extraParams = ""; + $page = max(1, intval($pg)); + + if ($tid === "16") { + $channelId = "1"; + $extraParams = "&three_category_id=27401"; + } else if ($tid === "5") { + $dataType = 2; + } + + // 处理筛选条件 + if (!empty($extend)) { + if (isset($extend['year'])) { + $extraParams .= "&market_release_date_level={$extend['year']}"; + } + } + + $url = "https://pcw-api.iqiyi.com/search/recommend/list?channel_id={$channelId}&data_type={$dataType}&page_id={$page}&ret_num=20{$extraParams}"; + + $jsonStr = $this->fetch($url, [], $this->getHeaders()); + $jsonData = json_decode($jsonStr, true); + + $videos = []; + if (isset($jsonData['data']['list'])) { + foreach ($jsonData['data']['list'] as $item) { + $vid = "{$item['channelId']}\${$item['albumId']}"; + $remarks = ""; + + if ($item['channelId'] == 1) { + $remarks = isset($item['score']) ? "{$item['score']}分" : ""; + } else if ($item['channelId'] == 2 || $item['channelId'] == 4) { + if (isset($item['latestOrder']) && isset($item['videoCount'])) { + $remarks = ($item['latestOrder'] == $item['videoCount']) ? + "{$item['latestOrder']}集全" : + "更新至{$item['latestOrder']}集"; + } else { + $remarks = $item['focus'] ?? ""; + } + } else { + $remarks = $item['period'] ?? ($item['focus'] ?? ""); + } + + $pic = isset($item['imageUrl']) ? str_replace(".jpg", "_390_520.jpg", $item['imageUrl']) : ""; + + $videos[] = [ + 'vod_id' => $vid, + 'vod_name' => $item['name'], + 'vod_pic' => $pic, + 'vod_remarks' => $remarks + ]; + } + } + + return $this->pageResult($videos, $page, 999999, 20); + } + + private function getPlaylists($channelId, $albumId, $data) { + $playlists = []; + $cid = intval($channelId ?: ($data['channelId'] ?? 0)); + + if ($cid === 1 || $cid === 5) { + // 电影或音乐 + if (isset($data['playUrl'])) { + $playlists[] = ['title' => $data['name'] ?? '正片', 'url' => $data['playUrl']]; + } + } else if ($cid === 6 && isset($data['period'])) { + // 综艺 + $qs = explode("-", (string)$data['period'])[0]; + $listUrl = "https://pcw-api.iqiyi.com/album/source/svlistinfo?cid=6&sourceid={$albumId}&timelist={$qs}"; + + $listResp = $this->fetch($listUrl, [], $this->getHeaders()); + $listJson = json_decode($listResp, true); + + if (isset($listJson['data'][$qs])) { + foreach ($listJson['data'][$qs] as $it) { + $title = $it['shortTitle'] ?? ($it['period'] ?? ($it['focus'] ?? "期{$it['order']}")); + $playlists[] = [ + 'title' => $title, + 'url' => $it['playUrl'] + ]; + } + } + } else { + // 电视剧、动漫等 + $listUrl = "https://pcw-api.iqiyi.com/albums/album/avlistinfo?aid={$albumId}&size=100&page=1"; + $listResp = $this->fetch($listUrl, [], $this->getHeaders()); + $listJson = json_decode($listResp, true); + + if (isset($listJson['data']['epsodelist'])) { + foreach ($listJson['data']['epsodelist'] as $item) { + $title = $item['shortTitle'] ?? ($item['title'] ?? (isset($item['order']) ? "第{$item['order']}集" : "集{$item['timelist']}")); + $playlists[] = [ + 'title' => $title, + 'url' => $item['playUrl'] ?? ($item['url'] ?? '') + ]; + } + + // 处理分页 + $total = $listJson['data']['total'] ?? 0; + if ($total > 100) { + $totalPages = ceil($total / 100); + for ($i = 2; $i <= $totalPages; $i++) { + $nextUrl = "https://pcw-api.iqiyi.com/albums/album/avlistinfo?aid={$albumId}&size=100&page={$i}"; + $nextResp = $this->fetch($nextUrl, [], $this->getHeaders()); + $nextJson = json_decode($nextResp, true); + + if (isset($nextJson['data']['epsodelist'])) { + foreach ($nextJson['data']['epsodelist'] as $item) { + $title = $item['shortTitle'] ?? ($item['title'] ?? (isset($item['order']) ? "第{$item['order']}集" : "集{$item['timelist']}")); + $playlists[] = [ + 'title' => $title, + 'url' => $item['playUrl'] ?? ($item['url'] ?? '') + ]; + } + } + } + } + } + } + return $playlists; + } + + public function detailContent($ids) { + $id = is_array($ids) ? $ids[0] : $ids; + $channelId = ""; + $albumId = $id; + + if (strpos($id, '$') !== false) { + $parts = explode('$', $id); + $channelId = $parts[0]; + $albumId = $parts[1]; + } + + // 获取视频基本信息 + $infoUrl = "https://pcw-api.iqiyi.com/video/video/videoinfowithuser/{$albumId}?agent_type=1&authcookie=&subkey={$albumId}&subscribe=1"; + $infoResp = $this->fetch($infoUrl, [], $this->getHeaders()); + $infoJson = json_decode($infoResp, true); + $data = $infoJson['data'] ?? []; + + // 获取播放列表 + $playlists = $this->getPlaylists($channelId, $albumId, $data); + + // 构建播放地址 + $playUrls = []; + foreach ($playlists as $item) { + if (!empty($item['url'])) { + $playUrls[] = "{$item['title']}\${$item['url']}"; + } + } + + $typeName = ''; + if (isset($data['categories'])) { + $names = array_map(function($it) { return $it['name']; }, $data['categories']); + $typeName = implode(',', $names); + } + + $area = ''; + if (isset($data['areas'])) { + $names = array_map(function($it) { return $it['name']; }, $data['areas']); + $area = implode(',', $names); + } + + $actors = ''; + if (isset($data['people']['main_charactor'])) { + $names = array_map(function($it) { return $it['name']; }, $data['people']['main_charactor']); + $actors = implode(',', $names); + } + + $director = ''; + if (isset($data['people']['director'])) { + $names = array_map(function($it) { return $it['name']; }, $data['people']['director']); + $director = implode(',', $names); + } + + $remarks = ""; + if (isset($data['latestOrder'])) { + $remarks = "更新至{$data['latestOrder']}集"; + } else { + $remarks = isset($data['period']) || count($playlists) > 0 ? count($playlists)."集" : ""; + } + + $vod = [ + 'vod_id' => $id, + 'vod_name' => $data['name'] ?? '未知标题', + 'type_name' => $typeName, + 'vod_year' => $data['formatPeriod'] ?? '', + 'vod_area' => $area, + 'vod_remarks' => $remarks, + 'vod_actor' => $actors, + 'vod_director' => $director, + 'vod_content' => $data['description'] ?? '暂无简介', + 'vod_pic' => isset($data['imageUrl']) ? str_replace(".jpg", "_480_270.jpg", $data['imageUrl']) : '', + 'vod_play_from' => count($playUrls) > 0 ? '爱奇艺视频' : '', + 'vod_play_url' => implode('#', $playUrls) + ]; + + return ['list' => [$vod]]; + } + + public function searchContent($key, $quick = false, $pg = 1) { + $page = max(1, intval($pg)); + $url = "https://search.video.iqiyi.com/o?if=html5&key=" . urlencode($key) . "&pageNum={$page}&pos=1&pageSize=20&site=iqiyi"; + + $response = $this->fetch($url, [], $this->getHeaders()); + $jsonData = json_decode($response, true); + + $videos = []; + if (isset($jsonData['data']['docinfos'])) { + foreach ($jsonData['data']['docinfos'] as $item) { + if (isset($item['albumDocInfo'])) { + $doc = $item['albumDocInfo']; + $channelId = isset($doc['channel']) ? explode(',', $doc['channel'])[0] : '0'; + $videos[] = [ + 'vod_id' => "{$channelId}\${$doc['albumId']}", + 'vod_name' => $doc['albumTitle'] ?? '', + 'vod_pic' => $doc['albumVImage'] ?? '', + 'vod_remarks' => $doc['tvFocus'] ?? ($doc['year'] ?? '') + ]; + } + } + } + + return $this->pageResult($videos, $page, count($videos) * 10, 20); // 搜索无法获取总数,简单估算 + } + + public function playerContent($flag, $id, $vipFlags = []) { + $playUrl = $id; + if (strpos($id, '$') !== false) { + $playUrl = explode('$', $id)[1]; + } + + // 壳子超级解析格式 + return [ + 'parse' => 1, + 'jx' => 1, + 'play_parse' => true, + 'parse_type' => '壳子超级解析', + 'parse_source' => '爱奇艺视频', + 'url' => $playUrl, + 'header' => json_encode([ + 'User-Agent' => $this->getHeaders()['User-Agent'], + 'Referer' => 'https://www.iqiyi.com', + 'Origin' => 'https://www.iqiyi.com' + ], JSON_UNESCAPED_UNICODE) + ]; + } +} + +(new Spider())->run(); diff --git "a/spider/php/\345\261\261\346\234\211\346\234\250\345\205\256 \341\265\210\341\266\273.php" "b/spider/php/\345\261\261\346\234\211\346\234\250\345\205\256 \341\265\210\341\266\273.php" new file mode 100644 index 00000000..7d6768f9 --- /dev/null +++ "b/spider/php/\345\261\261\346\234\211\346\234\250\345\205\256 \341\265\210\341\266\273.php" @@ -0,0 +1,176 @@ +<?php +/** + * 山有木兮 - PHP 适配版 (道长重构) + * 按照 BaseSpider 结构重写 + */ + +require_once __DIR__ . '/lib/spider.php'; + +class Spider extends BaseSpider { + + private $HOST = 'https://film.symx.club'; + + public function init($extend = '') { + $this->headers['User-Agent'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36'; + $this->headers['Sec-Ch-Ua'] = '"Google Chrome";v="143", "Chromium";v="143", "Not A(Brand";v="24"'; + $this->headers['Sec-Ch-Ua-Mobile'] = '?0'; + $this->headers['Sec-Ch-Ua-Platform'] = '"Windows"'; + $this->headers['Sec-Fetch-Dest'] = 'empty'; + $this->headers['Sec-Fetch-Mode'] = 'cors'; + $this->headers['Sec-Fetch-Site'] = 'same-origin'; + $this->headers['X-Platform'] = 'web'; + $this->headers['Accept'] = 'application/json, text/plain, */*'; + + if (!empty($extend) && strpos($extend, 'http') === 0) { + $this->HOST = rtrim($extend, '/'); + } + } + + private function getHeaders($referer = '/') { + $headers = $this->headers; + $headers['Referer'] = $this->HOST . $referer; + return $headers; + } + + public function homeContent($filter = []) { + $url = $this->HOST . "/api/category/top"; + $data = json_decode($this->fetch($url, [], $this->getHeaders()), true); + + $classes = []; + if (isset($data['data'])) { + foreach ($data['data'] as $item) { + $classes[] = [ + 'type_id' => strval($item['id']), + 'type_name' => $item['name'] + ]; + } + } + return ['class' => $classes]; + } + + public function homeVideoContent() { + $url = $this->HOST . "/api/film/category"; + $data = json_decode($this->fetch($url, [], $this->getHeaders()), true); + + $list = []; + if (isset($data['data'])) { + foreach ($data['data'] as $category) { + $filmList = $category['filmList'] ?? []; + foreach ($filmList as $film) { + $list[] = [ + 'vod_id' => strval($film['id']), + 'vod_name' => $film['name'], + 'vod_pic' => $film['cover'], + 'vod_remarks' => $film['doubanScore'] ?? '' + ]; + } + } + } + return ['list' => array_slice($list, 0, 30)]; + } + + public function categoryContent($tid, $pg = 1, $filter = [], $extend = []) { + $pageNum = max(1, intval($pg)); + $url = $this->HOST . "/api/film/category/list?area=&categoryId={$tid}&language=&pageNum={$pageNum}&pageSize=15&sort=updateTime&year="; + $data = json_decode($this->fetch($url, [], $this->getHeaders()), true); + + $list = []; + if (isset($data['data']['list'])) { + foreach ($data['data']['list'] as $item) { + $list[] = [ + 'vod_id' => strval($item['id']), + 'vod_name' => $item['name'], + 'vod_pic' => $item['cover'], + 'vod_remarks' => $item['updateStatus'] + ]; + } + } + + $total = $data['data']['total'] ?? 0; + return $this->pageResult($list, $pageNum, $total, 15); + } + + public function detailContent($ids) { + if (empty($ids)) return ['list' => []]; + $id = $ids[0]; // 只处理第一个ID + + $url = $this->HOST . "/api/film/detail?id=" . urlencode($id); + $data = json_decode($this->fetch($url, [], $this->getHeaders()), true); + + if (!isset($data['data'])) { + return ['list' => []]; + } + + $info = $data['data']; + $shows = []; + $play_urls = []; + + if (isset($info['playLineList'])) { + foreach ($info['playLineList'] as $line) { + $shows[] = $line['playerName']; + $urls = []; + if (isset($line['lines'])) { + foreach ($line['lines'] as $episode) { + $urls[] = $episode['name'] . '$' . $episode['id']; + } + } + $play_urls[] = implode('#', $urls); + } + } + + $vod = [ + 'vod_id' => $id, + 'vod_name' => $info['name'], + 'vod_pic' => $info['cover'], + 'vod_year' => $info['year'], + 'vod_area' => $info['other'], + 'vod_actor' => $info['actor'], + 'vod_director' => $info['director'], + 'vod_content' => $info['blurb'], + 'vod_score' => $info['doubanScore'], + 'vod_play_from' => implode('$$$', $shows), + 'vod_play_url' => implode('$$$', $play_urls), + 'type_name' => $info['vod_class'] ?? '' + ]; + + return ['list' => [$vod]]; + } + + public function searchContent($key, $quick = false, $pg = 1) { + $pageNum = max(1, intval($pg)); + $url = $this->HOST . "/api/film/search?keyword=" . urlencode($key) . "&pageNum={$pageNum}&pageSize=10"; + $data = json_decode($this->fetch($url, [], $this->getHeaders()), true); + + $list = []; + if (isset($data['data']['list'])) { + foreach ($data['data']['list'] as $item) { + $list[] = [ + 'vod_id' => strval($item['id']), + 'vod_name' => $item['name'], + 'vod_pic' => $item['cover'], + 'vod_remarks' => $item['updateStatus'], + 'vod_year' => $item['year'], + 'vod_area' => $item['area'], + 'vod_director' => $item['director'] + ]; + } + } + return $this->pageResult($list, $pageNum); + } + + public function playerContent($flag, $id, $vipFlags = []) { + $url = $this->HOST . "/api/line/play/parse?lineId=" . urlencode($id); + $data = json_decode($this->fetch($url, [], $this->getHeaders()), true); + + $playUrl = $data['data'] ?? ''; + + return [ + 'parse' => 0, + 'url' => $playUrl, + 'header' => ['User-Agent' => $this->headers['User-Agent']] + ]; + } +} + +// 运行爬虫 +(new Spider())->run(); diff --git "a/spider/php/\346\230\237\346\230\237\347\237\255\345\211\247 \341\265\210\341\266\273.php" "b/spider/php/\346\230\237\346\230\237\347\237\255\345\211\247 \341\265\210\341\266\273.php" new file mode 100644 index 00000000..81e3c888 --- /dev/null +++ "b/spider/php/\346\230\237\346\230\237\347\237\255\345\211\247 \341\265\210\341\266\273.php" @@ -0,0 +1,139 @@ +<?php +require_once __DIR__ . '/lib/spider.php'; + +class Spider extends BaseSpider { + private $HOST = 'http://read.api.duodutek.com'; + private $UA = 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.87 Safari/537.36'; + + // 固定的 API 参数 + private $COMMON_PARAMS = [ + "productId" => "2a8c14d1-72e7-498b-af23-381028eb47c0", + "vestId" => "2be070e0-c824-4d0e-a67a-8f688890cadb", + "channel" => "oppo19", + "osType" => "android", + "version" => "20", + "token" => "202509271001001446030204698626" + ]; + + protected function getHeaders() { + return [ + 'User-Agent: ' . $this->UA + ]; + } + + public function homeContent($filter) { + // 定义分类 + $classes = [ + ["type_id" => "1287", "type_name" => "甜宠"], + ["type_id" => "1288", "type_name" => "逆袭"], + ["type_id" => "1289", "type_name" => "热血"], + ["type_id" => "1290", "type_name" => "现代"], + ["type_id" => "1291", "type_name" => "古代"] + ]; + + // 首页推荐:取第一个分类的前几个视频 + $list = $this->categoryContent('1287', 1)['list']; + $list = array_slice($list, 0, 12); + + return [ + 'class' => $classes, + 'list' => $list, + 'filters' => (object)[] + ]; + } + + public function categoryContent($tid, $pg = 1, $filter = [], $extend = []) { + $apiUrl = $this->HOST . '/novel-api/app/pageModel/getResourceById'; + + $params = array_merge($this->COMMON_PARAMS, [ + "resourceId" => $tid, + "pageNum" => (string)$pg, + "pageSize" => "10" + ]); + + $url = $apiUrl . '?' . http_build_query($params); + $jsonStr = $this->fetch($url, [], $this->getHeaders()); + $jsonObj = json_decode($jsonStr, true); + + $list = []; + if ($jsonObj && isset($jsonObj['data']['datalist'])) { + foreach ($jsonObj['data']['datalist'] as $vod) { + $list[] = [ + // 仿照原 Python:id@@name@@introduction 存储 + 'vod_id' => $vod['id'] . '@@' . $vod['name'] . '@@' . ($vod['introduction'] ?? ''), + 'vod_name' => $vod['name'], + 'vod_pic' => $vod['icon'], + 'vod_remarks' => $vod['heat'] . '万播放' + ]; + } + } + + return $this->pageResult($list, $pg, 999, 10); + } + + public function detailContent($ids) { + $did = is_array($ids) ? $ids[0] : $ids; + $parts = explode('@@', $did); + if (count($parts) >= 2) { + $bookId = $parts[0]; + $bookName = $parts[1]; + $intro = $parts[2] ?? ''; + } else { + // 兼容旧格式 id@intro + $parts = explode('@', $did); + $bookId = $parts[0]; + $bookName = ''; + $intro = $parts[1] ?? ''; + } + + $apiUrl = $this->HOST . '/novel-api/basedata/book/getChapterList'; + $params = array_merge($this->COMMON_PARAMS, [ + "bookId" => $bookId + ]); + + $url = $apiUrl . '?' . http_build_query($params); + $jsonStr = $this->fetch($url, [], $this->getHeaders()); + $jsonObj = json_decode($jsonStr, true); + + $playUrls = []; + if ($jsonObj && isset($jsonObj['data'])) { + $chapters = $jsonObj['data']; + foreach ($chapters as $index => $chapter) { + // 提取短剧播放地址 + if (isset($chapter['shortPlayList'][0]['chapterShortPlayVoList'][0]['shortPlayUrl'])) { + $vUrl = $chapter['shortPlayList'][0]['chapterShortPlayVoList'][0]['shortPlayUrl']; + $epName = "第" . ($index + 1) . "集"; + $playUrls[] = $epName . '$' . $vUrl; + } + } + } + + $vod = [ + 'vod_id' => $did, + 'vod_name' => $bookName, // 由列表页带入 + 'vod_content' => $intro, + 'vod_play_from' => '短剧专线', + 'vod_play_url' => implode('#', $playUrls) + ]; + + return ['list' => [$vod]]; + } + + public function searchContent($key, $quick = false, $pg = 1) { + // 原 Python 代码中 searchContentPage 为 pass,故此处留空返回 + return $this->pageResult([], $pg); + } + + public function playerContent($flag, $id, $vipFlags = []) { + return [ + 'parse' => 0, // 直接播放 + 'url' => $id, + 'header' => [ + 'User-Agent' => $this->UA + ] + ]; + } +} + +// 运行爬虫 +(new Spider())->run(); diff --git "a/spider/php/\346\236\234\346\236\234 \341\265\210\341\266\273.php" "b/spider/php/\346\236\234\346\236\234 \341\265\210\341\266\273.php" new file mode 100644 index 00000000..1b308208 --- /dev/null +++ "b/spider/php/\346\236\234\346\236\234 \341\265\210\341\266\273.php" @@ -0,0 +1,252 @@ +<?php +require_once __DIR__ . '/lib/spider.php'; + +class Spider extends BaseSpider { + private $host = 'https://www.mgtv.com'; + + protected function getHeaders() { + return [ + 'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36', + 'Referer' => 'https://www.mgtv.com/', + 'Accept' => 'application/json, text/plain, */*', + 'Accept-Language' => 'zh-CN,zh;q=0.9,en;q=0.8', + 'Connection' => 'keep-alive' + ]; + } + + public function homeContent($filter) { + $classes = [ + ['type_id' => '3', 'type_name' => '电影'], + ['type_id' => '2', 'type_name' => '电视剧'], + ['type_id' => '1', 'type_name' => '综艺'], + ['type_id' => '50', 'type_name' => '动漫'], + ['type_id' => '51', 'type_name' => '纪录片'], + ['type_id' => '115', 'type_name' => '教育'], + ['type_id' => '10', 'type_name' => '少儿'] + ]; + + $filters = [ + '3' => [ + [ + 'key' => 'year', 'name' => '年份', 'value' => [ + ['n' => '全部', 'v' => 'all'], ['n' => '2025', 'v' => '2025'], ['n' => '2024', 'v' => '2024'], + ['n' => '2023', 'v' => '2023'], ['n' => '2022', 'v' => '2022'], ['n' => '2021', 'v' => '2021'], + ['n' => '2020', 'v' => '2020'], ['n' => '2019', 'v' => '2019'], ['n' => '2010-2019', 'v' => '2010-2019'], + ['n' => '2000-2009', 'v' => '2000-2009'] + ] + ], + [ + 'key' => 'sort', 'name' => '排序', 'value' => [ + ['n' => '综合', 'v' => 'c1'], ['n' => '最新', 'v' => 'c2'], ['n' => '最热', 'v' => 'c4'] + ] + ] + ], + '2' => [ + [ + 'key' => 'year', 'name' => '年份', 'value' => [ + ['n' => '全部', 'v' => 'all'], ['n' => '2025', 'v' => '2025'], ['n' => '2024', 'v' => '2024'], + ['n' => '2023', 'v' => '2023'], ['n' => '2022', 'v' => '2022'], ['n' => '2021', 'v' => '2021'], + ['n' => '2020', 'v' => '2020'] + ] + ], + [ + 'key' => 'sort', 'name' => '排序', 'value' => [ + ['n' => '综合', 'v' => 'c1'], ['n' => '最新', 'v' => 'c2'], ['n' => '最热', 'v' => 'c4'] + ] + ] + ], + '1' => [ + [ + 'key' => 'sort', 'name' => '排序', 'value' => [ + ['n' => '综合', 'v' => 'c1'], ['n' => '最新', 'v' => 'c2'], ['n' => '最热', 'v' => 'c4'] + ] + ] + ], + '50' => [ + [ + 'key' => 'sort', 'name' => '排序', 'value' => [ + ['n' => '综合', 'v' => 'c1'], ['n' => '最新', 'v' => 'c2'], ['n' => '最热', 'v' => 'c4'] + ] + ] + ] + ]; + + return [ + 'class' => $classes, + 'filters' => $filters + ]; + } + + public function categoryContent($tid, $pg = 1, $filter = [], $extend = []) { + $page = max(1, intval($pg)); + $baseUrl = 'https://pianku.api.mgtv.com/rider/list/pcweb/v3'; + + $params = [ + 'platform' => 'pcweb', + 'channelId' => $tid, + 'pn' => $page, + 'pc' => '20', + 'hudong' => '1', + '_support' => '10000000', + 'kind' => 'a1', + 'area' => 'a1' + ]; + + if (!empty($extend)) { + if (isset($extend['year']) && $extend['year'] !== 'all') { + $params['year'] = $extend['year']; + } + if (isset($extend['sort'])) { + $params['sort'] = $extend['sort']; + } + if (isset($extend['chargeInfo'])) { + $params['chargeInfo'] = $extend['chargeInfo']; + } + } + + $url = $baseUrl . '?' . http_build_query($params); + $response = $this->fetch($url, [], $this->getHeaders()); + $json = json_decode($response, true); + + $videos = []; + if (isset($json['data']['hitDocs']) && is_array($json['data']['hitDocs'])) { + foreach ($json['data']['hitDocs'] as $item) { + $videos[] = [ + 'vod_id' => $item['playPartId'] ?? '', + 'vod_name' => $item['title'] ?? '', + 'vod_pic' => $item['img'] ?? '', + 'vod_remarks' => $item['updateInfo'] ?? ($item['rightCorner']['text'] ?? '') + ]; + } + } + + $totalHit = $json['data']['totalHit'] ?? 0; + return $this->pageResult($videos, $page, $totalHit, 20); + } + + public function detailContent($ids) { + $videoId = is_array($ids) ? $ids[0] : $ids; + + // 获取视频基本信息 + $infoUrl = "https://pcweb.api.mgtv.com/video/info?video_id={$videoId}"; + $infoResponse = $this->fetch($infoUrl, [], $this->getHeaders()); + $infoJson = json_decode($infoResponse, true); + $infoData = $infoJson['data']['info'] ?? []; + + $vod = [ + 'vod_id' => $videoId, + 'vod_name' => $infoData['title'] ?? '', + 'type_name' => $infoData['root_kind'] ?? '', + 'vod_actor' => '', + 'vod_year' => $infoData['release_time'] ?? '', + 'vod_content' => $infoData['desc'] ?? '', + 'vod_remarks' => $infoData['time'] ?? '', + 'vod_pic' => $infoData['img'] ?? '', + 'vod_play_from' => '芒果TV', + 'vod_play_url' => '' + ]; + + // 分页获取所有剧集 + $pageSize = 50; + $allEpisodes = []; + + // 获取第一页 + $firstPageUrl = "https://pcweb.api.mgtv.com/episode/list?video_id={$videoId}&page=1&size={$pageSize}"; + $firstResponse = $this->fetch($firstPageUrl, [], $this->getHeaders()); + $firstJson = json_decode($firstResponse, true); + $firstData = $firstJson['data'] ?? []; + + if (isset($firstData['list']) && is_array($firstData['list'])) { + $allEpisodes = array_merge($allEpisodes, $firstData['list']); + $totalPages = $firstData['total_page'] ?? 1; + + if ($totalPages > 1) { + for ($i = 2; $i <= $totalPages; $i++) { + $pageUrl = "https://pcweb.api.mgtv.com/episode/list?video_id={$videoId}&page={$i}&size={$pageSize}"; + // 简单串行获取,避免并发复杂性 + $resp = $this->fetch($pageUrl, [], $this->getHeaders()); + $data = json_decode($resp, true); + if (isset($data['data']['list']) && is_array($data['data']['list'])) { + $allEpisodes = array_merge($allEpisodes, $data['data']['list']); + } + } + } + } + + $playUrls = []; + if (!empty($allEpisodes)) { + // 过滤 + $validEpisodes = array_filter($allEpisodes, function($item) { + return isset($item['isIntact']) && ($item['isIntact'] === "1" || $item['isIntact'] === 1); + }); + + // 排序 + usort($validEpisodes, function($a, $b) { + return intval($a['order'] ?? 0) - intval($b['order'] ?? 0); + }); + + foreach ($validEpisodes as $item) { + $name = $item['t4'] ?? ($item['t3'] ?? ($item['title'] ?? ("第" . ($item['order'] ?? '?') . "集"))); + $playLink = isset($item['url']) ? "https://www.mgtv.com{$item['url']}" : ''; + + if ($playLink) { + $playUrls[] = "{$name}\${$playLink}"; + } + } + } + + $vod['vod_play_url'] = implode('#', $playUrls); + + return ['list' => [$vod]]; + } + + public function searchContent($key, $quick = false, $pg = 1) { + $page = max(1, intval($pg)); + $searchUrl = "https://mobileso.bz.mgtv.com/msite/search/v2?q=" . urlencode($key) . "&pn={$page}&pc=20"; + + $response = $this->fetch($searchUrl, [], $this->getHeaders()); + $json = json_decode($response, true); + $data = $json['data'] ?? []; + + $videos = []; + if (isset($data['contents']) && is_array($data['contents'])) { + foreach ($data['contents'] as $group) { + if (($group['type'] ?? '') === 'media' && isset($group['data']) && is_array($group['data'])) { + foreach ($group['data'] as $item) { + if (($item['source'] ?? '') === 'imgo') { + if (preg_match('/\/(\d+)\.html/', $item['url'], $match)) { + $videos[] = [ + 'vod_id' => $match[1], + 'vod_name' => isset($item['title']) ? str_replace(['<B>', '</B>'], '', $item['title']) : '', + 'vod_pic' => $item['img'] ?? '', + 'vod_remarks' => isset($item['desc']) ? implode(' ', $item['desc']) : '' + ]; + } + } + } + } + } + } + + return $this->pageResult($videos, $page, count($videos) * 10, 20); + } + + public function playerContent($flag, $id, $vipFlags = []) { + // 壳子超级解析格式 + return [ + 'parse' => 1, + 'jx' => 1, + 'play_parse' => true, + 'parse_type' => '壳子超级解析', + 'parse_source' => '芒果TV2', + 'url' => $id, + 'header' => json_encode([ + 'User-Agent' => $this->getHeaders()['User-Agent'], + 'Referer' => 'https://www.mgtv.com', + 'Origin' => 'https://www.mgtv.com' + ], JSON_UNESCAPED_UNICODE) + ]; + } +} + +(new Spider())->run(); diff --git "a/spider/php/\347\225\252\350\214\204\345\260\217\350\257\264 \341\265\210\341\266\273[\344\271\246].php" "b/spider/php/\347\225\252\350\214\204\345\260\217\350\257\264 \341\265\210\341\266\273[\344\271\246].php" new file mode 100644 index 00000000..b1c65a94 --- /dev/null +++ "b/spider/php/\347\225\252\350\214\204\345\260\217\350\257\264 \341\265\210\341\266\273[\344\271\246].php" @@ -0,0 +1,353 @@ +<?php +require_once __DIR__ . '/lib/spider.php'; + +class Spider extends BaseSpider { + private const HOST = 'https://fanqienovel.com'; + private const API_HOST = 'https://qkfqapi.vv9v.cn'; + private const UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36'; + + private $startPage = 1; + + public function init($extend = '') { + $this->startPage = 1; + } + + public function homeContent($filter = []) { + $url = self::HOST . '/api/author/book/category_list/v0/'; + $json = $this->fetchJson($url); + + $classes = []; + $filters = []; + + // 默认"全部"分类 + $classes[] = [ + 'type_name' => '全部', + 'type_id' => '-1' + ]; + + if (isset($json['data'])) { + $grouped = []; + foreach ($json['data'] as $item) { + $label = $item['label']; + if (!isset($grouped[$label])) { + $grouped[$label] = ['names' => [], 'ids' => []]; + } + $grouped[$label]['names'][] = $item['name']; + $grouped[$label]['ids'][] = $item['category_id']; + } + + foreach ($grouped as $label => $data) { + $classes[] = [ + 'type_name' => $label, + 'type_id' => $label + ]; + + $filterItems = []; + foreach ($data['names'] as $index => $name) { + $filterItems[] = ['n' => $name, 'v' => $data['ids'][$index]]; + } + + $filters[$label] = [ + [ + 'key' => 'category_id', + 'name' => '筛选', + 'value' => $filterItems + ] + ]; + } + } + + return [ + 'class' => $classes, + 'filters' => (object)$filters + ]; + } + + public function categoryContent($tid, $pg = 1, $filter = [], $extend = []) { + // url: /api/author/library/book_list/v0/?page_count=18&page_index=(fypage-1)&gender=1&category_id=fyclass&creation_status=-1&word_count=-1&book_type=-1&sort=0#fyfilter + + $categoryId = $extend['category_id'] ?? ''; + + // 如果 tid 是 '-1' (全部),且没有选筛选,则可能需要默认值或者不做筛选 + // JS 逻辑:if (MY_CATE !== '-1') ... else input = input.split('#')[0] + // 这里简化:构建 API 参数 + + $params = [ + 'page_count' => 18, + 'page_index' => $pg - 1, + 'gender' => 1, + 'category_id' => $categoryId ?: '-1', // 默认 -1 ? JS 中如果是全部,input直接去掉了 category_id 参数? + // 观察 JS: + // if (MY_CATE !== '-1') { category_id = input.split('#')[1]; replace... } + // 意思是如果不是全部,必须选筛选? + // 实际上 API 支持 category_id 参数。 + 'creation_status' => -1, + 'word_count' => -1, + 'book_type' => -1, + 'sort' => 0 + ]; + + // 如果 tid 不是 '-1' 且没有 category_id (即直接点了一级分类但没点筛选) + // JS 中 filters 定义了 value 是 category_id。 + // 如果用户只点了大类(如“都市”),tid="都市"。 + // 但 API 需要具体的 category_id(数字)。 + // JS 逻辑里 filters 的 key 是 '筛选',value 是数字 ID。 + // 如果 tid 是 '全部',category_id 应该传什么? + // 抓包看:全部 -> category_id 不传或 -1。 + // + // 修正:DS源里 filter_url 是 '{{fl.筛选}}',即 category_id 直接取值。 + // 如果 $categoryId 为空,且 $tid 不是 -1,说明用户只选了大类没选子类。 + // 这时应该怎么办?看 JS: + // JS 的 filters 是必须选的吗? + // 让我们假设如果 $tid 不是 -1,但 $categoryId 为空,我们可能无法请求,或者默认取该大类下的第一个? + // 这里的 filters 构造里,value 就是 category_id。 + + if ($tid !== '-1' && empty($categoryId)) { + // 尝试获取该分类下的第一个 ID?或者 API 支持直接传 label? + // 实际上 fanqienovel API 需要数字 ID。 + // 简单起见,如果未选筛选,默认 -1 (全部) + $params['category_id'] = -1; + } + + $url = self::HOST . '/api/author/library/book_list/v0/?' . http_build_query($params); + $json = $this->fetchJson($url); + + $videos = []; + if (isset($json['data']['book_list'])) { + foreach ($json['data']['book_list'] as $item) { + $videos[] = [ + 'vod_id' => $item['book_id'], + 'vod_name' => $this->decodeText($item['book_name']), + 'vod_pic' => 'http://p6-novel.byteimg.com/large/' . $item['thumb_uri'], + 'vod_remarks' => $this->decodeText($item['author']), + ]; + } + } + + return $this->pageResult($videos, $pg, 18, 18); // total 未知,假设无限 + } + + public function detailContent($ids) { + $id = $ids[0]; + $url = self::HOST . "/page/$id"; + + // 模拟 PC UA + $html = $this->fetch($url, ['headers' => ['User-Agent' => self::UA]]); + + // 提取 window.__INITIAL_STATE__= + if (preg_match('/window\.__INITIAL_STATE__=(.+?);<\/script>/s', $html, $matches) || + preg_match('/window\.__INITIAL_STATE__=(.+?)(?:;|$)/s', $html, $matches)) { + $jsonStr = $matches[1]; + // 替换 undefined 为 null + $jsonStr = str_replace('undefined', 'null', $jsonStr); + $json = json_decode($jsonStr, true); + + if (isset($json['page'])) { + $info = $json['page']; + $bookInfo = $info['bookInfo'] ?? $info; // 结构可能变动 + + // 书名等信息 + $vod = [ + 'vod_id' => $id, + 'vod_name' => $info['bookName'] ?? '', + 'vod_pic' => $info['thumbUri'] ?? '', + 'vod_content' => $info['abstract'] ?? '', + 'vod_remarks' => $info['lastChapterTitle'] ?? '', + 'vod_director' => $info['author'] ?? '', + 'vod_play_from' => '番茄小说', + ]; + + // 章节列表 + $playList = []; + $chapters = $info['chapterListWithVolume'] ?? []; + + // chapterListWithVolume 是个二维数组 [[章节...], [章节...]] + foreach ($chapters as $volume) { + foreach ($volume as $chapter) { + $title = $chapter['title']; + $itemId = $chapter['itemId']; + $playList[] = "$title$" . $itemId . '@' . $title; + } + } + + $vod['vod_play_url'] = implode('#', $playList); + return ['list' => [$vod]]; + } + } + return ['list' => []]; + } + + public function searchContent($key, $quick = false, $pg = 1) { + // URL: /api/search?key=**&tab_type=3&offset=((fypage-1)*10) + // HOST: API_HOST + $offset = ($pg - 1) * 10; + $url = self::API_HOST . "/api/search?key=" . urlencode($key) . "&tab_type=3&offset=$offset"; + + $json = $this->fetchJson($url); + + $videos = []; + // 寻找 search_tabs[5] -> tab_type=3 ? JS 中是 search_tabs[5] 但 API 参数是 tab_type=3 + // 遍历寻找 tab_type = 3 的 tab + $targetData = []; + if (isset($json['data']['search_tabs'])) { + foreach ($json['data']['search_tabs'] as $tab) { + // JS 取下标 5,我们严谨点判断 + // 或者 API 返回的结构里 tab_type 字段 + // 假设结构类似 + if (isset($tab['data'])) { + // 检查第一条数据是否有 book_data 且是小说 + // 简单粗暴:合并所有 tab 的 data? 不,JS 明确是小说 tab + // 暂时取第一个包含 book_data 的 + if (!empty($tab['data']) && isset($tab['data'][0]['book_data'])) { + $targetData = $tab['data']; + break; + } + } + } + } + + foreach ($targetData as $item) { + if (isset($item['book_data'][0])) { + $book = $item['book_data'][0]; + $videos[] = [ + 'vod_id' => $book['book_id'], + 'vod_name' => $book['book_name'], + 'vod_pic' => $book['thumb_url'], + 'vod_remarks' => $book['author'], + 'vod_content' => $book['book_abstract'] ?? $book['abstract'] ?? '' + ]; + } + } + + return $this->pageResult($videos, $pg, 10, 10); + } + + public function playerContent($flag, $id, $vipFlags = []) { + // id: itemId@title + $parts = explode('@', $id); + $itemId = $parts[0]; + $title = $parts[1] ?? ''; + + // Handle Title$ItemId format + if (strpos($itemId, '$') !== false) { + $subParts = explode('$', $itemId); + $itemId = $subParts[1]; + if (empty($title)) { + $title = $subParts[0]; + } + } + + $url = self::API_HOST . "/api/content?tab=小说&item_id=$itemId"; + + // 随机 Cookie + $cookie = $this->getFqCookie(); + $json = $this->fetchJson($url, ['headers' => ['Cookie' => $cookie]]); + + $content = ''; + if (isset($json['data']['content'])) { + $content = $json['data']['content']; + } + + // 构造 novel:// 协议返回,或者直接文本 + // DZ 风格播放器可能不支持 novel://,通常直接返回文本或 html + // 如果是小说,通常返回 parse=0, url=text... + // 这里模仿 JS 返回:novel://json_string + // 还是直接返回文本内容比较通用? + // 为了兼容,我们返回文本内容,如果客户端支持 novel:// 最好,不支持就直接显示 + // 现在的 PHP 爬虫通常返回 content-type: text/plain + // 但 playerContent 需要返回标准结构 + + // 构造响应 + return [ + 'parse' => 0, + 'playUrl' => '', + 'url' => $url, // 仅作记录 + 'header' => (object)[], + // 如果客户端支持直接显示文本内容,通常放在 header 或其他字段? + // 实际上,播放接口返回 content 可能需要客户端特殊处理 + // 这里我们返回一个 data url 或者 模拟的 html + // 参考 JS: return {parse: 0, url: 'novel://' + ret} + 'url' => 'novel://' . json_encode(['title' => $title, 'content' => $content], JSON_UNESCAPED_UNICODE) + ]; + } + + private function getFqCookie() { + $cookies = [ + 'novel_web_id=78444872394737941004', + 'novel_web_id=69258894393744181011', + 'novel_web_id=77130880221809081001', + 'novel_web_id=64945771562463261001', + 'novel_web_id=78444872394737941004', + 'novel_web_id=0000000000004011402', + 'novel_web_id=0000000303614711402', + 'novel_web_id=0144211303614711401', + 'novel_web_id=0144211303614711402', + 'novel_web_id=0144211303614711403', + 'novel_web_id=0144211303614711406', + 'novel_web_id=7357767624615331361', + 'novel_web_id=7357767624615331362', + 'novel_web_id=7357767624615331365', + ]; + return $cookies[array_rand($cookies)]; + } + + // 解密函数 + private function decodeText($text, $type = 0) { + $charset = []; + if ($type === 0) { + // ... 巨大的数组 ... + $charset = ['体', 'y', '十', '现', '快', '便', '话', '却', '月', '物', '水', '的', '放', '知', '爱', '万', '', '表', '风', '理', 'O', '老', '也', 'p', '常', '克', '平', '几', '最', '主', '她', 's', '将', '法', '情', 'o', '光', 'a', '我', '呢', 'J', '员', '太', '每', '望', '受', '教', 'w', '利', '军', '已', 'U', '人', '如', '变', '得', '要', '少', '斯', '门', '电', 'm', '男', '没', 'A', 'K', '国', '时', '中', '走', '么', '何', '口', '小', '向', '问', '轻', 'T', 'd', '神', '下', '间', '车', 'f', 'G', '度', 'D', '又', '大', '面', '远', '就', '写', 'j', '给', '通', '起', '实', 'E', '', '它', '去', 'S', '到', '道', '数', '吃', '们', '加', 'P', '是', '无', '把', '事', '西', '多', '界', '', '发', '新', '外', '活', '解', '孩', '只', '作', '前', 'Y', '尔', '经', '', 'u', '心', '告', '父', '等', 'Q', '民', '全', '这', '9', '果', '安', '', 'i', '母', '8', 'r', '说', '任', '先', '和', '地', 'C', '张', '战', '场', 'g', '像', 'c', 'q', '你', '使', '', '样', '总', '目', 'x', '性', '处', '音', '头', '', '应', '乐', '关', '能', '花', 'l', '当', '名', '手', '4', '重', '字', '声', '力', '友', '然', '生', '代', '内', '里', '本', '回', '真', '入', '师', '象', '', '0', '点', 'R', '亲', 'V', '种', '动', '英', '命', 'Z', 'h', 'X', '做', '特', '边', '高', '有', 'B', '为', '期', '自', '年', '马', '认', '出', '接', '至', 'H', '正', '方', '感', '所', '明', '者', '稜', 'F', '住', '学', '还', '分', '意', '更', '其', 'n', '但', '比', '觉', '以', '由', '死', '家', '让', '失', '士', 'L', '2', 'I', '金', '叫', '身', '报', '听', 'W', '再', '原', '山', '海', '白', '很', '见', '5', '直', '位', '第', '工', '个', '开', '岁', '好', '用', '都', '于', '可', '同', '3', '次', '四', '', '日', '信', '与', '女', '笑', '满', '并', '部', '什', '不', '从', '或', '机', '此', '', '了', '记', '三', 'e', '些', 'b', 'N', '夫', '会', '才', '儿', '眼', '两', '美', '被', '一', '公', '来', '立', 'z', '长', '对', '己', '看', 'k', '许', '因', '相', '色', '后', '往', '打', '结', '格', '过', '世', '气', '7', '子', '条', '在', '书', '之', '定', 'v', '拉', '成', '进', '带', '着', '东', '上', '想', '天', '他', '妈', '1', '文', '而', '路', '那', '别', '德', '6', 'M', 't', '行', '候', '难']; + } else if ($type === 1) { + $charset = ['', 's', '', '作', '口', '在', '他', '能', '并', 'B', '士', '4', 'U', '克', '才', '正', '们', '字', '声', '高', '全', '尔', '活', '者', '动', '其', '主', '报', '多', '望', '放', 'h', 'w', '次', '年', '', '中', '3', '特', '于', '十', '入', '要', '男', '同', 'G', '面', '分', '方', 'K', '什', '再', '教', '本', '己', '结', '1', '等', '世', 'N', '', '说', 'g', 'u', '期', 'Z', '外', '美', 'M', '行', '给', '9', '文', '将', '两', '许', '张', '友', '0', '英', '应', '向', '像', '此', '白', '安', '少', '何', '打', '气', '常', '定', '间', '花', '见', '孩', '它', '直', '风', '数', '使', '道', '第', '水', '已', '女', '山', '解', 'd', 'P', '的', '通', '关', '性', '叫', '儿', 'L', '妈', '问', '回', '神', '来', 'S', '', '四', '里', '前', '国', '些', 'O', 'v', 'l', 'A', '心', '平', '自', '无', '军', '光', '代', '是', '好', '却', 'c', '得', '种', '就', '意', '先', '立', 'z', '子', '过', 'Y', 'j', '表', '', '么', '所', '接', '了', '名', '金', '受', 'J', '满', '眼', '没', '部', '那', 'm', '每', '车', '度', '可', 'R', '斯', '经', '现', '门', '明', 'V', '如', '走', '命', 'y', '6', 'E', '战', '很', '上', 'f', '月', '西', '7', '长', '夫', '想', '话', '变', '海', '机', 'x', '到', 'W', '一', '成', '生', '信', '笑', '但', '父', '开', '内', '东', '马', '日', '小', '而', '后', '带', '以', '三', '几', '为', '认', 'X', '死', '员', '目', '位', '之', '学', '远', '人', '音', '呢', '我', 'q', '乐', '象', '重', '对', '个', '被', '别', 'F', '也', '书', '稜', 'D', '写', '还', '因', '家', '发', '时', 'i', '或', '住', '德', '当', 'o', 'I', '比', '觉', '然', '吃', '去', '公', 'a', '老', '亲', '情', '体', '太', 'b', '万', 'C', '电', '理', '', '失', '力', '更', '拉', '物', '着', '原', '她', '工', '实', '色', '感', '记', '看', '出', '相', '路', '大', '你', '候', '2', '和', '', '与', 'p', '样', '新', '只', '便', '最', '不', '进', 'T', 'r', '做', '格', '母', '总', '爱', '身', '师', '轻', '知', '往', '加', '从', '', '天', 'e', 'H', '', '听', '场', '由', '快', '边', '让', '把', '任', '8', '条', '头', '事', '至', '起', '点', '真', '手', '这', '难', '都', '界', '用', '法', 'n', '处', '下', '又', 'Q', '告', '地', '5', 'k', 't', '岁', '有', '会', '果', '利', '民']; + } else if ($type === 2) { + $charset = ['D', '在', '主', '特', '家', '军', '然', '表', '场', '4', '要', '只', 'v', '和', '?', '6', '别', '还', 'g', '现', '儿', '岁', '?', '?', '此', '象', '月', '3', '出', '战', '工', '相', 'o', '男', '直', '失', '世', 'F', '都', '平', '文', '什', 'V', 'O', '将', '真', 'T', '那', '当', '?', '会', '立', '些', 'u', '是', '十', '张', '学', '气', '大', '爱', '两', '命', '全', '后', '东', '性', '通', '被', '1', '它', '乐', '接', '而', '感', '车', '山', '公', '了', '常', '以', '何', '可', '话', '先', 'p', 'i', '叫', '轻', 'M', '士', 'w', '着', '变', '尔', '快', 'l', '个', '说', '少', '色', '里', '安', '花', '远', '7', '难', '师', '放', 't', '报', '认', '面', '道', 'S', '?', '克', '地', '度', 'I', '好', '机', 'U', '民', '写', '把', '万', '同', '水', '新', '没', '书', '电', '吃', '像', '斯', '5', '为', 'y', '白', '几', '日', '教', '看', '但', '第', '加', '候', '作', '上', '拉', '住', '有', '法', 'r', '事', '应', '位', '利', '你', '声', '身', '国', '问', '马', '女', '他', 'Y', '比', '父', 'x', 'A', 'H', 'N', 's', 'X', '边', '美', '对', '所', '金', '活', '回', '意', '到', 'z', '从', 'j', '知', '又', '内', '因', '点', 'Q', '三', '定', '8', 'R', 'b', '正', '或', '夫', '向', '德', '听', '更', '?', '得', '告', '并', '本', 'q', '过', '记', 'L', '让', '打', 'f', '人', '就', '者', '去', '原', '满', '体', '做', '经', 'K', '走', '如', '孩', 'c', 'G', '给', '使', '物', '?', '最', '笑', '部', '?', '员', '等', '受', 'k', '行', '一', '条', '果', '动', '光', '门', '头', '见', '往', '自', '解', '成', '处', '天', '能', '于', '名', '其', '发', '总', '母', '的', '死', '手', '入', '路', '进', '心', '来', 'h', '时', '力', '多', '开', '已', '许', 'd', '至', '由', '很', '界', 'n', '小', '与', 'Z', '想', '代', '么', '分', '生', '口', '再', '妈', '望', '次', '西', '风', '种', '带', 'J', '?', '实', '情', '才', '这', '?', 'E', '我', '神', '格', '长', '觉', '间', '年', '眼', '无', '不', '亲', '关', '结', '0', '友', '信', '下', '却', '重', '己', '老', '2', '音', '字', 'm', '呢', '明', '之', '前', '高', 'P', 'B', '目', '太', 'e', '9', '起', '稜', '她', '也', 'W', '用', '方', '子', '英', '每', '理', '便', '四', '数', '期', '中', 'C', '外', '样', 'a', '海', '们', '任']; + } + + // JS: _decodeText2 + // text = text.replace(reg, ($0, $1) => z[('0x' + $1) - 1000]); + // reg = /%uE([0-9a-fA-F]{3})/gi + // 58344 (decimal) = E3E8 (hex) + // CODE_ST = 58344 + // index = charCode - 58344 + // JS charset array index logic: + // z[('0x' + $1) - 1000] ??? + // JS code: z[('0x' + $1) - 1000] + // If $1 is '3E8' (1000), then index is 0. + // 'E3E8' -> $1='3E8'. 0x3E8 = 1000. 1000 - 1000 = 0. + // So offset is indeed related to 0xE3E8. + + // PHP Logic: + // Iterate string, find characters in range [0xE3E8, 0xE55B] (approx) + // Or use regex like JS. + // In PHP, unicode characters can be matched or we can convert string to unicode code points. + // + // Better to use preg_replace_callback with unicode escape sequence? + // But the input text might be normal UTF-8 string, not escaped %uXXXX. + // JS's `_decodeText2` first calls `escape(text)`. + // So we should do similar: convert string to unicode hex entities or iterate chars. + + $result = ''; + $len = mb_strlen($text, 'UTF-8'); + for ($i = 0; $i < $len; $i++) { + $char = mb_substr($text, $i, 1, 'UTF-8'); + $code = mb_ord($char, 'UTF-8'); + + // CODE_ST = 58344 (0xE3E8) + // CODE_ED = 58715 (0xE55B) + if ($code >= 58344 && $code <= 58715) { + $index = $code - 58344; + if (isset($charset[$index])) { + $result .= $charset[$index]; + } else { + $result .= $char; + } + } else { + $result .= $char; + } + } + return $result; + } +} + +// 运行爬虫 +(new Spider())->run(); diff --git "a/spider/php/\347\225\252\350\214\204\346\274\253\347\224\273 \341\265\210\341\266\273[\347\224\273].php" "b/spider/php/\347\225\252\350\214\204\346\274\253\347\224\273 \341\265\210\341\266\273[\347\224\273].php" new file mode 100644 index 00000000..5af97ca6 --- /dev/null +++ "b/spider/php/\347\225\252\350\214\204\346\274\253\347\224\273 \341\265\210\341\266\273[\347\224\273].php" @@ -0,0 +1,232 @@ +<?php +/** + * 番茄漫画 ᵈᶻ.php + * 对应源: 番茄漫画[画].js + */ + +require_once __DIR__ . '/lib/spider.php'; + +class Spider extends BaseSpider { + private const HOST = 'https://qkfqapi.vv9v.cn'; + private const UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36'; + + private $startPage = 1; + + public function init($extend = '') { + $this->startPage = 1; + } + + public function homeContent($filter = []) { + $url = self::HOST . '/api/discover/style?tab=漫画'; + $json = $this->fetchJson($url); + + $classes = []; + if (isset($json['data']) && is_array($json['data'])) { + foreach ($json['data'] as $item) { + if (isset($item['url']) && trim($item['url'])) { + $classes[] = [ + 'type_id' => $item['url'], // URL作为ID + 'type_name' => $item['title'] ?? '未知分类', + ]; + } + } + } + + return [ + 'class' => $classes, + 'filters' => (object)[] + ]; + } + + public function categoryContent($tid, $pg = 1, $filter = [], $extend = []) { + // tid 是类似 /api/discover?tab=漫画&type=7...&page=1 的URL + // 需要替换 page 参数 + $url = self::HOST . $tid; + if (strpos($tid, 'http') === 0) { + $url = $tid; + } + + // 简单的页码替换逻辑:假设URL中包含 page=1 + // 如果包含 page=x,替换为 page=$pg + if (preg_match('/page=\d+/', $url)) { + $url = preg_replace('/page=\d+/', 'page=' . $pg, $url); + } else { + // 如果没有page参数,追加 + $sep = (strpos($url, '?') !== false) ? '&' : '?'; + $url .= $sep . 'page=' . $pg; + } + + $json = $this->fetchJson($url); + $list = $this->parseList($json); + + return $this->pageResult($list, $pg, 0, 10); // limit 10 + } + + public function detailContent($ids) { + $id = $ids[0]; + $url = self::HOST . "/api/book?book_id=$id"; + $json = $this->fetchJson($url); + + $vod = [ + 'vod_id' => $id, + 'vod_name' => '', + 'vod_pic' => '', + 'type_name' => '', + 'vod_year' => '', + 'vod_area' => '', + 'vod_remarks' => '', + 'vod_actor' => '', + 'vod_director' => '', + 'vod_content' => '', + ]; + + if (isset($json['data']['data'])) { + $data = $json['data']['data']; + $vod['vod_name'] = $data['book_name'] ?? ''; + $vod['type_name'] = $data['category'] ?? ''; + $vod['vod_pic'] = $data['thumb_url'] ?? ''; + $vod['vod_content'] = $data['abstract'] ?? ''; + $vod['vod_remarks'] = $data['sub_info'] ?? ''; + $vod['vod_director'] = $data['author'] ?? ''; + + // 章节列表 + // 这里需要获取章节列表,JS中使用了 jsonStr.parseX.data.data.chapterListWithVolume + // 假设API返回结构一致 + $chapters = []; + if (isset($data['chapterListWithVolume'])) { + // 可能是嵌套数组,需要扁平化 + foreach ($data['chapterListWithVolume'] as $volume) { + if (is_array($volume)) { + foreach ($volume as $chapter) { + $chapters[] = $chapter; + } + } + } + } + + // 如果扁平化失败,尝试直接读取(视API返回而定) + if (empty($chapters) && isset($data['chapter_list'])) { + $chapters = $data['chapter_list']; + } + + $playUrls = []; + foreach ($chapters as $ch) { + $title = $ch['title'] ?? '未知章节'; + $itemId = $ch['itemId'] ?? $ch['item_id'] ?? ''; + $playUrls[] = "$title$$itemId@$title"; + } + + $vod['vod_play_from'] = '番茄漫画'; + $vod['vod_play_url'] = implode('#', $playUrls); + } + + return ['list' => [$vod]]; + } + + public function searchContent($key, $quick = false, $pg = 1) { + $offset = ($pg - 1) * 10; + $url = self::HOST . "/api/search?key=" . urlencode($key) . "&tab_type=8&offset=$offset"; + $json = $this->fetchJson($url); + + $list = []; + if (isset($json['data']['search_tabs'][3]['data'])) { + $items = $json['data']['search_tabs'][3]['data']; + foreach ($items as $it) { + if (isset($it['book_data'][0])) { + $book = $it['book_data'][0]; + $list[] = [ + 'vod_id' => $book['book_id'] ?? '', + 'vod_name' => $book['book_name'] ?? '', + 'vod_pic' => $book['thumb_url'] ?? '', + 'vod_remarks' => $book['author'] ?? '', + 'vod_content' => $book['book_abstract'] ?? $book['abstract'] ?? '', + ]; + } + } + } + + return $this->pageResult($list, $pg, 0, 10); + } + + public function playerContent($flag, $id, $vipFlags = []) { + // id: itemId@title + $parts = explode('@', $id); + $itemId = $parts[0]; + + $url = self::HOST . "/api/content?tab=漫画&item_id=$itemId&show_html=0"; + $cookie = $this->getFqCookie(); + $json = $this->fetchJson($url, ['headers' => ['Cookie' => $cookie]]); + + $pics = []; + if (isset($json['data']['images'])) { + $images = $json['data']['images']; + if (is_string($images)) { + if (preg_match_all('/<img[^>]+src=[\'"]([^\'"]+)[\'"]/i', $images, $matches)) { + $pics = $matches[1]; + } + } elseif (is_array($images)) { + foreach ($images as $img) { + if (isset($img['src'])) { + $pics[] = $img['src']; + } elseif (isset($img['url'])) { + $pics[] = $img['url']; + } + } + } + } + + if (empty($pics)) { + return ['parse' => 0, 'url' => '', 'header' => (object)[]]; + } + + // 漫画通常使用 pics:// 协议 + return ['parse' => 0, 'url' => 'pics://' . implode('&&', $pics), 'header' => (object)[]]; + } + + // 辅助方法:解析列表 + private function parseList($json) { + $list = []; + $data = $json['data'] ?? []; + if (isset($data['data'])) { + $data = $data['data']; + } + + if (is_array($data)) { + foreach ($data as $item) { + if ($item && (isset($item['book_name']) || isset($item['title']))) { + $list[] = [ + 'vod_id' => $item['book_id'] ?? $item['id'] ?? '', + 'vod_name' => $item['book_name'] ?? $item['title'] ?? '', + 'vod_pic' => $item['thumb_url'] ?? $item['cover'] ?? '', + 'vod_remarks' => $item['author'] ?? $item['category'] ?? '', + 'vod_content' => $item['abstract'] ?? $item['description'] ?? '', + ]; + } + } + } + return $list; + } + + private function getFqCookie() { + $cookies = [ + 'novel_web_id=78444872394737941004', + 'novel_web_id=69258894393744181011', + 'novel_web_id=77130880221809081001', + 'novel_web_id=64945771562463261001', + 'novel_web_id=78444872394737941004', + 'novel_web_id=0000000000004011402', + 'novel_web_id=0000000303614711402', + 'novel_web_id=0144211303614711401', + 'novel_web_id=0144211303614711402', + 'novel_web_id=0144211303614711403', + 'novel_web_id=0144211303614711406', + 'novel_web_id=7357767624615331361', + 'novel_web_id=7357767624615331362', + 'novel_web_id=7357767624615331365', + ]; + return $cookies[array_rand($cookies)]; + } +} + +// 运行爬虫 +(new Spider())->run(); diff --git "a/spider/php/\347\273\205\345\243\253\346\274\253\347\224\273 \341\265\210\341\266\273[\347\224\273].php" "b/spider/php/\347\273\205\345\243\253\346\274\253\347\224\273 \341\265\210\341\266\273[\347\224\273].php" new file mode 100644 index 00000000..a0931cae --- /dev/null +++ "b/spider/php/\347\273\205\345\243\253\346\274\253\347\224\273 \341\265\210\341\266\273[\347\224\273].php" @@ -0,0 +1,255 @@ +<?php +/** + * 绅士漫画 + */ +require_once __DIR__ . '/lib/spider.php'; + +class Spider extends BaseSpider { + + protected const HOST = 'https://www.wn06.ru'; + + public function getName() { + return "绅士漫画"; + } + + public function init($extend = "") { + $this->headers['Referer'] = self::HOST . '/'; + } + + public function homeContent($filter) { + $classes = [ + ["type_name" => "月榜", "type_id" => "rank_month"], + ["type_name" => "周榜", "type_id" => "rank_week"], + ["type_name" => "日榜", "type_id" => "rank_day"], + ["type_name" => "同人志", "type_id" => "1"], + ["type_name" => "韩漫", "type_id" => "20"], + ["type_name" => "单行本", "type_id" => "9"], + ["type_name" => "杂志&短篇", "type_id" => "10"] + ]; + + return ["class" => $classes, "filters" => (object)[]]; + } + + public function homeVideoContent() { + return $this->categoryContent("rank_month", 1, [], []); + } + + public function categoryContent($tid, $pg = 1, $filter = [], $extend = []) { + if ($tid == "rank_month") { + $url = self::HOST . "/albums-favorite_ranking-page-{$pg}-type-month.html"; + } elseif ($tid == "rank_week") { + $url = self::HOST . "/albums-favorite_ranking-page-{$pg}-type-week.html"; + } elseif ($tid == "rank_day") { + $url = self::HOST . "/albums-favorite_ranking-page-{$pg}-type-day.html"; + } else { + $url = self::HOST . "/albums-index-page-{$pg}-cate-{$tid}.html"; + } + + $html = $this->fetch($url); + + // Parse list items + $items = $this->pdfa($html, '.gallary_wrap ul li'); + $videos = []; + + foreach ($items as $item) { + $vid = $this->pd($item, '.info .title a&&href'); + $name = $this->pdfh($item, '.info .title a&&Text'); + $cover = $this->pd($item, '.pic_box img&&src'); + $info_text = $this->pdfh($item, '.info .info_col&&Text'); + $remark = ""; + if (preg_match('/(\d+)張圖片/', $info_text, $match)) { + $remark = $match[1] . "页"; + } + + $videos[] = [ + "vod_id" => $vid, + "vod_name" => $name, + "vod_pic" => $cover, + "vod_remarks" => $remark + ]; + } + + return $this->pageResult($videos, $pg, 999999, 20); + } + + public function detailContent($ids) { + $vid = $ids[0]; + $url = (strpos($vid, 'http') === 0) ? $vid : self::HOST . $vid; + + $html = $this->fetch($url); + + // Title + $name = $this->pdfh($html, 'h2&&Text'); + if (empty($name)) $name = "未知"; + + // Cover + $cover = $this->pd($html, '.uwthumb img&&src'); + if (empty($cover)) { + $cover = $this->pd($html, '.cover img&&src'); + } + + // Desc + $desc = $this->pdfh($html, '.uwconn p||.info p&&Text'); + + // Pagination logic + $max_page = 1; + $aid = ""; + + if (preg_match('/aid-(\d+)/', $url, $match)) { + $aid = $match[1]; + } + + $paginator_links = $this->pdfa($html, '.paginator a'); + foreach ($paginator_links as $link) { + $href = $this->pdfh($link, 'a&&href'); + + if (!$aid && preg_match('/aid-(\d+)/', $href, $m)) { + $aid = $m[1]; + } + + if (preg_match('/page-(\d+)/', $href, $m)) { + $p = intval($m[1]); + if ($p > $max_page) { + $max_page = $p; + } + } + } + + $vod_play_url_list = []; + if ($max_page == 1) { + $vod_play_url_list[] = "第1页$" . $url; + } else { + for ($i = 1; $i <= $max_page; $i++) { + if ($aid) { + $page_url = self::HOST . "/photos-index-page-{$i}-aid-{$aid}.html"; + $vod_play_url_list[] = "第{$i}页$" . $page_url; + } elseif ($i == 1) { + $vod_play_url_list[] = "第1页$" . $url; + } + } + } + + $play_url_str = implode("#", $vod_play_url_list); + + return [ + "list" => [[ + "vod_id" => $vid, + "vod_name" => $name, + "vod_pic" => $cover, + "type_name" => "漫画", + "vod_year" => "", + "vod_area" => "", + "vod_remarks" => "共{$max_page}页", + "vod_actor" => "", + "vod_director" => "", + "vod_content" => $desc, + "vod_play_from" => '阅读', + "vod_play_url" => $play_url_str + ]] + ]; + } + + public function searchContent($key, $quick = false, $pg = 1) { + $url = self::HOST . "/search/?q=" . urlencode($key) . "&f=_all&s=create_time_DESC&syn=yes&page={$pg}"; + $html = $this->fetch($url); + + $items = $this->pdfa($html, '.gallary_wrap ul li'); + $videos = []; + + foreach ($items as $item) { + $vid = $this->pd($item, '.info .title a&&href'); + $name = $this->pdfh($item, '.info .title a&&Text'); + $cover = $this->pd($item, '.pic_box img&&src'); + + $info_text = $this->pdfh($item, '.info .info_col&&Text'); + $remark = ""; + if (preg_match('/(\d+)張圖片/', $info_text, $match)) { + $remark = $match[1] . "页"; + } + + $videos[] = [ + "vod_id" => $vid, + "vod_name" => $name, + "vod_pic" => $cover, + "vod_remarks" => $remark + ]; + } + + return ["list" => $videos]; + } + + public function playerContent($flag, $id, $vipFlags = []) { + $url = $id; + $headers = $this->headers; + $headers['Referer'] = $url; + + $html = $this->fetch($url, ['headers' => $headers]); + + $items = $this->pdfa($html, '.gallary_wrap.tb ul li'); + $img_info_list = []; + $prefix_url = ""; + + foreach ($items as $item) { + $seq = $this->pdfh($item, 'span.name.tb&&Text'); + $src = $this->pdfh($item, 'img&&src'); + + if (!$src) continue; + + $ext = "jpg"; + $parts = explode('.', $src); + if (count($parts) > 1) { + $last = end($parts); + $ext = explode('?', $last)[0]; + } + + if (!$prefix_url && strpos($src, "wnimg1") !== false) { + $last_slash = strrpos($src, '/'); + if ($last_slash !== false) { + $prefix_url = substr($src, 0, $last_slash + 1); + } + } + + $img_info_list[] = [ + "name" => $seq, + "ext" => $ext, + "raw_src" => $src + ]; + } + + // Sort + usort($img_info_list, function($a, $b) { + $na = is_numeric($a['name']) ? intval($a['name']) : 0; + $nb = is_numeric($b['name']) ? intval($b['name']) : 0; + return $na <=> $nb; + }); + + $final_images = []; + foreach ($img_info_list as $item) { + if ($prefix_url) { + $full_url = "{$prefix_url}{$item['name']}.{$item['ext']}"; + } else { + $full_url = $item['raw_src']; + } + + if (strpos($full_url, "tu.petatt.cn") !== false) continue; + + if (strpos($full_url, '//') === 0) { + $full_url = 'https:' . $full_url; + } + + $final_images[] = $full_url; + } + + $novel_data = implode("&&", $final_images); + + return [ + "parse" => 0, + "playUrl" => "", + "url" => "pics://{$novel_data}", + "header" => "" + ]; + } +} + +// 自动运行 +(new Spider())->run(); diff --git "a/spider/php/\350\205\276\350\205\276 \341\265\210\341\266\273.php" "b/spider/php/\350\205\276\350\205\276 \341\265\210\341\266\273.php" new file mode 100644 index 00000000..5eb5ec4e --- /dev/null +++ "b/spider/php/\350\205\276\350\205\276 \341\265\210\341\266\273.php" @@ -0,0 +1,868 @@ +<?php +require_once __DIR__ . '/lib/spider.php'; + +class Spider extends BaseSpider { + private $host = 'https://v.qq.com'; + private $apiHost = 'https://pbaccess.video.qq.com'; + + public function homeContent($filter) { + $classes = [ + ['type_id' => '100173', 'type_name' => '电影'], + ['type_id' => '100113', 'type_name' => '电视剧'], + ['type_id' => '100109', 'type_name' => '综艺'], + ['type_id' => '100105', 'type_name' => '纪录片'], + ['type_id' => '100119', 'type_name' => '动漫'], + ['type_id' => '100150', 'type_name' => '少儿'], + ['type_id' => '110755', 'type_name' => '短剧'] + ]; + + $filters = [ + '100173' => [ + [ + 'key' => 'iyear', 'name' => '年份', 'value' => [ + ['n' => '全部', 'v' => '-1'], ['n' => '2025', 'v' => '2025'], ['n' => '2024', 'v' => '2024'], + ['n' => '2023', 'v' => '2023'], ['n' => '2022', 'v' => '2022'], ['n' => '2021', 'v' => '2021'], + ['n' => '2020', 'v' => '2020'] + ] + ], + [ + 'key' => 'sort', 'name' => '排序', 'value' => [ + ['n' => '综合', 'v' => '75'], ['n' => '最新', 'v' => '76'], ['n' => '最热', 'v' => '74'] + ] + ] + ], + '100113' => [ + [ + 'key' => 'iyear', 'name' => '年份', 'value' => [ + ['n' => '全部', 'v' => '-1'], ['n' => '2025', 'v' => '2025'], ['n' => '2024', 'v' => '2024'], + ['n' => '2023', 'v' => '2023'], ['n' => '2022', 'v' => '2022'], ['n' => '2021', 'v' => '2021'], + ['n' => '2020', 'v' => '2020'] + ] + ], + [ + 'key' => 'sort', 'name' => '排序', 'value' => [ + ['n' => '综合', 'v' => '75'], ['n' => '最新', 'v' => '76'], ['n' => '最热', 'v' => '74'] + ] + ] + ], + '100109' => [ + [ + 'key' => 'sort', 'name' => '排序', 'value' => [ + ['n' => '综合', 'v' => '75'], ['n' => '最新', 'v' => '76'], ['n' => '最热', 'v' => '74'] + ] + ] + ], + '100119' => [ + [ + 'key' => 'sort', 'name' => '排序', 'value' => [ + ['n' => '综合', 'v' => '75'], ['n' => '最新', 'v' => '76'], ['n' => '最热', 'v' => '74'] + ] + ] + ] + ]; + + return [ + 'class' => $classes, + 'filters' => $filters + ]; + } + + public function homeVideoContent() { + return ['list' => []]; + } + + public function categoryContent($tid, $pg = 1, $filter = [], $extend = []) { + $page = max(1, intval($pg)); + $offset = ($page - 1) * 21; + + // 构建列表页URL(使用原始的页面URL结构) + $url = $this->host . '/x/bu/pagesheet/list'; + $params = [ + '_all' => '1', + 'append' => '1', + 'channel' => $this->getChannelByTid($tid), + 'listpage' => '1', + 'offset' => $offset, + 'pagesize' => '21', + 'iarea' => '-1' + ]; + + // 添加排序参数 + if (isset($extend['sort']) && $extend['sort'] !== '-1') { + $params['sort'] = $extend['sort']; + } else { + $params['sort'] = '75'; // 默认综合排序 + } + + // 添加其他筛选参数 + if (isset($extend['iyear']) && $extend['iyear'] !== '-1') { + $params['iyear'] = $extend['iyear']; + } + + $fullUrl = $url . '?' . http_build_query($params); + + // 使用新的PHP解析逻辑 + $videos = $this->parseListPage($fullUrl); + + return $this->pageResult($videos, $page, 99999, 21); + } + + /** + * 根据类型ID获取频道名称 + */ + private function getChannelByTid($tid) { + $map = [ + '100173' => 'movie', // 电影 + '100113' => 'tv', // 电视剧 + '100109' => 'variety', // 综艺 + '100105' => 'doco', // 纪录片 + '100119' => 'cartoon', // 动漫 + '100150' => 'child', // 少儿 + '110755' => 'choice' // 短剧(用精选代替) + ]; + + return $map[$tid] ?? 'movie'; + } + + /** + * 解析列表页(使用新的PHP逻辑) + */ + private function parseListPage($url) { + $result = []; + + try { + // 1. 获取网页内容 + $html = $this->fetch($url); + + // 2. 使用DOMDocument解析HTML + libxml_use_internal_errors(true); + $dom = new DOMDocument(); + @$dom->loadHTML(mb_convert_encoding($html, 'HTML-ENTITIES', 'UTF-8')); + $xpath = new DOMXPath($dom); + + // 3. 查找所有列表项 + $listItems = $xpath->query('//div[contains(@class, "list_item")]'); + + foreach ($listItems as $item) { + // 提取标题 (img的alt属性) + $imgElements = $xpath->query('.//img', $item); + $title = ''; + if ($imgElements->length > 0) { + $node = $imgElements->item(0); + if ($node instanceof DOMElement) { + $title = $node->getAttribute('alt'); + $title = html_entity_decode($title, ENT_QUOTES | ENT_HTML5, 'UTF-8'); + } + } + + // 提取图片 (img的src属性) + $pic = ''; + if ($imgElements->length > 0) { + $node = $imgElements->item(0); + if ($node instanceof DOMElement) { + $pic = $node->getAttribute('src'); + // 确保图片URL完整 + if ($pic && !preg_match('/^https?:\/\//', $pic)) { + $pic = $this->urlJoin($url, $pic); + } + } + } + + // 提取描述 (a标签的文本) + $aElements = $xpath->query('.//a', $item); + $desc = ''; + if ($aElements->length > 0) { + $desc = trim($aElements->item(0)->textContent); + $desc = html_entity_decode($desc, ENT_QUOTES | ENT_HTML5, 'UTF-8'); + } + + // 提取链接 (a标签的data-float属性) + $link = ''; + if ($aElements->length > 0) { + $node = $aElements->item(0); + if ($node instanceof DOMElement) { + $link = $node->getAttribute('data-float'); + // 处理链接,获取CID + if ($link) { + $cid = $this->extractCidFromUrl($link); + if ($cid) { + $link = $cid; + } + } + } + } + + // 如果链接为空,尝试从其他属性提取 + if (empty($link) && $aElements->length > 0) { + $node = $aElements->item(0); + if ($node instanceof DOMElement) { + $href = $node->getAttribute('href'); + if ($href) { + $cid = $this->extractCidFromUrl($href); + if ($cid) { + $link = $cid; + } + } + } + } + + // 添加到结果数组 + if (!empty($link) && !empty($title)) { + $result[] = [ + 'vod_id' => $link, + 'vod_name' => $this->cleanText($title), + 'vod_pic' => $pic, + 'vod_remarks' => $this->cleanText($desc) + ]; + } + } + + // 如果DOM解析失败,尝试使用正则表达式 + if (empty($result)) { + $result = $this->parseListWithRegex($html, $url); + } + + } catch (\Exception $e) { + error_log("解析列表页失败: " . $e->getMessage() . " URL: " . $url); + // 尝试备用方法 + $result = $this->parseListWithRegex($html ?? '', $url); + } + + return $result; + } + + /** + * 使用正则表达式解析列表页(备用方法) + */ + private function parseListWithRegex($html, $baseUrl) { + $result = []; + + // 匹配列表项 + $pattern = '/<div[^>]*class="[^"]*list_item[^"]*"[^>]*>(.*?)<\/div>/is'; + preg_match_all($pattern, $html, $itemMatches, PREG_SET_ORDER); + + foreach ($itemMatches as $item) { + $itemHtml = $item[1]; + + // 提取标题 + preg_match('/<img[^>]*alt="([^"]*)"[^>]*>/i', $itemHtml, $titleMatch); + $title = $titleMatch[1] ?? ''; + $title = html_entity_decode($title, ENT_QUOTES | ENT_HTML5, 'UTF-8'); + + // 提取图片 + preg_match('/<img[^>]*src="([^"]*)"[^>]*>/i', $itemHtml, $picMatch); + $pic = $picMatch[1] ?? ''; + if ($pic && !preg_match('/^https?:\/\//', $pic)) { + $pic = $this->urlJoin($baseUrl, $pic); + } + + // 提取链接 + preg_match('/<a[^>]*data-float="([^"]*)"[^>]*>/i', $itemHtml, $linkMatch); + $link = $linkMatch[1] ?? ''; + if (empty($link)) { + preg_match('/<a[^>]*href="([^"]*)"[^>]*>/i', $itemHtml, $hrefMatch); + $link = $hrefMatch[1] ?? ''; + } + + // 提取CID + $cid = ''; + if ($link) { + $cid = $this->extractCidFromUrl($link); + } + + // 提取描述 + preg_match('/<a[^>]*>(.*?)<\/a>/is', $itemHtml, $descMatch); + $desc = $descMatch[1] ?? ''; + $desc = strip_tags($desc); + $desc = html_entity_decode($desc, ENT_QUOTES | ENT_HTML5, 'UTF-8'); + + if (!empty($cid) && !empty($title)) { + $result[] = [ + 'vod_id' => $cid, + 'vod_name' => $this->cleanText($title), + 'vod_pic' => $pic, + 'vod_remarks' => $this->cleanText($desc) + ]; + } + } + + return $result; + } + + /** + * 从URL中提取CID + */ + private function extractCidFromUrl($url) { + // 处理多种URL格式 + $patterns = [ + '/\/cover\/([a-zA-Z0-9]+)\.html/', // /cover/CID.html + '/\/([a-zA-Z0-9]+)\.html$/', // /CID.html + '/cid=([a-zA-Z0-9]+)/', // cid=CID + '/\/([a-zA-Z0-9]+)\//', // /CID/ + '/\/detail\/m\/([a-zA-Z0-9]+)\.html/' // /detail/m/CID.html + ]; + + foreach ($patterns as $pattern) { + if (preg_match($pattern, $url, $matches)) { + return $matches[1]; + } + } + + return ''; + } + + /** + * URL拼接辅助函数 + */ + private function urlJoin($baseUrl, $relativePath) { + if (empty($relativePath)) { + return $relativePath; + } + + if (preg_match('/^https?:\/\//', $relativePath)) { + return $relativePath; + } + + $baseParts = parse_url($baseUrl); + $basePath = isset($baseParts['path']) ? dirname($baseParts['path']) : '/'; + + if (strpos($relativePath, '/') === 0) { + // 绝对路径 + return $baseParts['scheme'] . '://' . $baseParts['host'] . $relativePath; + } else { + // 相对路径 + $newPath = rtrim($basePath, '/') . '/' . ltrim($relativePath, '/'); + return $baseParts['scheme'] . '://' . $baseParts['host'] . $newPath; + } + } + + /** + * 清理文本,移除多余空格和换行 + */ + private function cleanText($text) { + if (empty($text)) { + return ''; + } + + $text = trim($text); + $text = preg_replace('/\s+/', ' ', $text); // 替换多个空格为单个空格 + $text = preg_replace('/[\r\n]+/', ' ', $text); // 移除换行 + return $text; + } + + // ================== 以下是原有的搜索逻辑,完全保持不变 ================== + + public function detailContent($ids) { + $videoId = is_array($ids) ? $ids[0] : $ids; + + // 获取视频基本信息 + $infoUrl = $this->apiHost . '/trpc.universal_backend_service.page_server_rpc.PageServer/GetPageData'; + + $infoBody = [ + "page_params" => [ + "req_from" => "web", + "cid" => $videoId, + "vid" => "", + "lid" => "", + "page_type" => "detail_operation", + "page_id" => "detail_page_introduction" + ], + "has_cache" => 1 + ]; + + $infoResponse = $this->fetch($infoUrl . '?video_appid=3000010&vplatform=2&vversion_name=8.2.96', [ + CURLOPT_POST => true, + CURLOPT_POSTFIELDS => json_encode($infoBody), + CURLOPT_HTTPHEADER => [ + 'Content-Type: application/json', + 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', + 'Referer: ' . $this->host . '/', + 'Origin: ' . $this->host + ] + ]); + + $infoJson = json_decode($infoResponse, true); + $infoData = $infoJson['data'] ?? []; + + // 提取视频详情 + $vod = [ + 'vod_id' => $videoId, + 'vod_name' => '', + 'type_name' => '', + 'vod_actor' => '', + 'vod_year' => '', + 'vod_content' => '', + 'vod_remarks' => '', + 'vod_pic' => '', + 'vod_play_from' => '腾讯视频', + 'vod_play_url' => '' + ]; + + // 提取基本信息 + if (isset($infoData['module_list_datas'][0]['module_datas'][0]['item_data_lists']['item_datas'][0])) { + $detailData = $infoData['module_list_datas'][0]['module_datas'][0]['item_data_lists']['item_datas'][0]; + $itemParams = $detailData['item_params'] ?? []; + + $vod['vod_name'] = $itemParams['title'] ?? ''; + $vod['type_name'] = $itemParams['sub_genre'] ?? ''; + $vod['vod_year'] = $itemParams['year'] ?? ''; + $vod['vod_content'] = $itemParams['cover_description'] ?? ''; + $vod['vod_remarks'] = $itemParams['holly_online_time'] ?? $itemParams['hotval'] ?? ''; + $vod['vod_pic'] = $itemParams['image_url'] ?? ''; + + // 提取演员信息 + if (isset($detailData['sub_items']['star_list']['item_datas'])) { + $actors = []; + foreach ($detailData['sub_items']['star_list']['item_datas'] as $star) { + $actors[] = $star['item_params']['name'] ?? ''; + } + $vod['vod_actor'] = implode(',', $actors); + } + } + + // 方法1:使用分页获取所有剧集 + $playUrls = $this->getAllEpisodes($videoId); + + // 方法2:如果方法1失败,尝试备用方法 + if (empty($playUrls)) { + $playUrls = $this->getEpisodesByTab($videoId); + } + + // 方法3:如果还是没有剧集,可能是电影 + if (empty($playUrls) && !empty($videoId)) { + $playUrls[] = "正片\${$videoId}"; + } + + $vod['vod_play_url'] = implode('#', $playUrls); + + return ['list' => [$vod]]; + } + + public function searchContent($key, $quick = false, $pg = 1) { + $page = max(1, intval($pg)); + $videos = []; + + // 使用原有的搜索逻辑 + $searchData = $this->vodSearch($key, $page - 1); // JavaScript代码中页码从0开始 + + if (!empty($searchData)) { + foreach ($searchData as $item) { + $videos[] = [ + 'vod_id' => $item['id'] ?? '', + 'vod_name' => $item['title'] ?? '', + 'vod_pic' => $item['img'] ?? '', + 'vod_remarks' => $item['desc'] ?? '' + ]; + } + } + + // 计算总页数(假设每页30条) + $total = count($videos) > 0 ? 999 : 0; + $limit = 30; + + return $this->pageResult($videos, $page, $total, $limit); + } + + public function playerContent($flag, $id, $vipFlags = []) { + // 解析播放地址格式:cid@vid 或 cid + if (strpos($id, '@') !== false) { + $parts = explode('@', $id); + $cid = $parts[0]; + $vid = $parts[1]; + $url = "{$this->host}/x/cover/{$cid}/{$vid}.html"; + } else { + // 只有cid,可能是电影 + $url = "{$this->host}/x/cover/{$id}.html"; + } + + return [ + 'parse' => 1, + 'jx' => 1, + 'play_parse' => true, + 'parse_type' => '壳子超级解析', + 'parse_source' => '腾讯视频', + 'url' => $url, + 'header' => json_encode([ + 'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', + 'Referer' => $this->host, + 'Origin' => $this->host + ], JSON_UNESCAPED_UNICODE) + ]; + } + + /** + * 执行搜索(原有的搜索逻辑) + */ + private function vodSearch($keyword, $page = 0) { + $url = 'https://pbaccess.video.qq.com/trpc.videosearch.mobile_search.MultiTerminalSearch/MbSearch?vplatform=2'; + + $body = json_encode([ + "version" => "25042201", + "clientType" => 1, + "filterValue" => "", + "uuid" => "B1E50847-D25F-4C4B-BBA0-36F0093487F6", + "retry" => 0, + "query" => $keyword, + "pagenum" => $page, + "isPrefetch" => true, + "pagesize" => 30, + "queryFrom" => 0, + "searchDatakey" => "", + "transInfo" => "", + "isneedQc" => true, + "preQid" => "", + "adClientInfo" => "", + "extraInfo" => [ + "isNewMarkLabel" => "1", + "multi_terminal_pc" => "1", + "themeType" => "1", + "sugRelatedIds" => "{}", + "appVersion" => "" + ] + ]); + + $response = $this->fetch($url, [ + CURLOPT_POST => true, + CURLOPT_POSTFIELDS => $body, + CURLOPT_HTTPHEADER => [ + 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.139 Safari/537.36', + 'Content-Type: application/json', + 'Origin: https://v.qq.com', + 'Referer: https://v.qq.com/' + ] + ]); + + return $this->parseSearchResult($response); + } + + /** + * 解析搜索结果(原有的搜索逻辑) + */ + private function parseSearchResult($html) { + $d = []; + $seenIds = []; + + try { + $json = json_decode($html, true); + + // 处理normalList + if (isset($json['data']['normalList']['itemList'])) { + $this->processItemList($json['data']['normalList']['itemList'], $d, $seenIds); + } + + // 处理areaBoxList + if (isset($json['data']['areaBoxList'])) { + foreach ($json['data']['areaBoxList'] as $box) { + if (isset($box['itemList'])) { + $this->processItemList($box['itemList'], $d, $seenIds); + } + } + } + } catch (\Exception $e) { + error_log("搜索解析出错: " . $e->getMessage()); + } + + return $d; + } + + /** + * 处理项目列表(原有的搜索逻辑) + */ + private function processItemList($itemList, &$d, &$seenIds) { + $nonMainContentKeywords = [ + ':', '#', '特辑', '"', '剪辑', '片花', '独家', '专访', '纯享', + '制作', '幕后', '宣传', 'MV', '主题曲', '插曲', '彩蛋', + '精彩', '集锦', '盘点', '回顾', '解说', '评测', '反应', 'reaction' + ]; + + foreach ($itemList as $it) { + if (isset($it['doc']['id'], $it['videoInfo']['title'])) { + $itemId = $it['doc']['id']; + $videoInfo = $it['videoInfo']; + $title = $videoInfo['title'] ?? ''; + + // 检查是否主要内容 + if (!$this->isMainContent($title, $nonMainContentKeywords)) { + continue; + } + + // 检查是否为QQ平台 + if (!$this->isQQPlatform($videoInfo['playSites'] ?? [])) { + continue; + } + + // 去重检查 + if (in_array($itemId, $seenIds)) { + continue; + } + + $seenIds[] = $itemId; + + $d[] = [ + 'id' => $itemId, + 'title' => $title, + 'img' => $videoInfo['imgUrl'] ?? '', + 'desc' => $videoInfo['secondLine'] ?? '' + ]; + } + } + } + + /** + * 检查是否主要内容(原有的搜索逻辑) + */ + private function isMainContent($title, $nonMainContentKeywords) { + if (empty($title)) { + return false; + } + + // 检查是否包含HTML标签(如<em>) + if (strpos($title, '<') !== false) { + return false; + } + + // 检查是否包含非主要内容关键词 + foreach ($nonMainContentKeywords as $keyword) { + if (strpos($title, $keyword) !== false) { + return false; + } + } + + return true; + } + + /** + * 检查是否为QQ平台(原有的搜索逻辑) + */ + private function isQQPlatform($playSites) { + if (empty($playSites) || !is_array($playSites)) { + return true; // 如果没有平台信息,默认保留 + } + + foreach ($playSites as $site) { + if (isset($site['enName']) && strtolower($site['enName']) === 'qq') { + return true; + } + } + + return false; + } + + // ================== 其他辅助方法保持不变 ================== + + private function buildFilterParams($params) { + $result = []; + foreach ($params as $key => $value) { + if ($value !== '-1' && $value !== '' && $value !== null) { + $result[] = "{$key}={$value}"; + } + } + return empty($result) ? 'sort=75' : implode('&', $result); + } + + /** + * 方法1:分页获取所有剧集 + */ + private function getAllEpisodes($videoId) { + $allEpisodes = []; + $pageSize = 50; + $pageNum = 1; + $hasMore = true; + + while ($hasMore) { + $episodeUrl = $this->apiHost . '/trpc.video_detail_svr.video_detail_svr.VideoDetail/GetEpisodeList'; + + $episodeBody = [ + "cid" => $videoId, + "vid" => "", + "req_from" => "web", + "page_context" => "", + "page_size" => $pageSize, + "page_num" => $pageNum, + "order" => 1 + ]; + + $episodeResponse = $this->fetch($episodeUrl . '?video_appid=3000010&vplatform=2&vversion_name=8.2.96', [ + CURLOPT_POST => true, + CURLOPT_POSTFIELDS => json_encode($episodeBody), + CURLOPT_HTTPHEADER => [ + 'Content-Type: application/json', + 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', + 'Referer: ' . $this->host . '/', + 'Origin: ' . $this->host + ] + ]); + + $episodeJson = json_decode($episodeResponse, true); + $episodeData = $episodeJson['data'] ?? []; + + if (isset($episodeData['item_data_lists']['item_datas'])) { + $episodes = $episodeData['item_data_lists']['item_datas']; + + if (!empty($episodes)) { + foreach ($episodes as $item) { + $itemId = $item['item_id'] ?? ''; + $itemParams = $item['item_params'] ?? []; + + if (!empty($itemId)) { + $title = $itemParams['title'] ?? $itemParams['subtitle'] ?? "第" . ($itemParams['order'] ?? '?') . "集"; + $allEpisodes[] = "{$title}\${$videoId}@{$itemId}"; + } + } + + // 检查是否还有更多页 + $hasMore = isset($episodeData['has_more']) && $episodeData['has_more'] == 1; + $pageNum++; + } else { + $hasMore = false; + } + } else { + $hasMore = false; + } + + // 防止无限循环 + if ($pageNum > 20) { + break; + } + } + + return $allEpisodes; + } + + /** + * 方法2:通过tab标签获取所有剧集 + */ + private function getEpisodesByTab($videoId) { + $allEpisodes = []; + + $tabUrl = $this->apiHost . '/trpc.universal_backend_service.page_server_rpc.PageServer/GetPageData'; + + $tabBody = [ + "page_params" => [ + "req_from" => "web_vsite", + "page_id" => "vsite_episode_list", + "page_type" => "detail_operation", + "id_type" => "1", + "cid" => $videoId, + "vid" => "", + "lid" => "", + "page_context" => "", + "detail_page_type" => "1" + ], + "has_cache" => 1 + ]; + + $tabResponse = $this->fetch($tabUrl . '?video_appid=3000010&vplatform=2&vversion_name=8.2.96', [ + CURLOPT_POST => true, + CURLOPT_POSTFIELDS => json_encode($tabBody), + CURLOPT_HTTPHEADER => [ + 'Content-Type: application/json', + 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', + 'Referer: ' . $this->host . '/', + 'Origin: ' . $this->host + ] + ]); + + $tabJson = json_decode($tabResponse, true); + $tabData = $tabJson['data'] ?? []; + + if (isset($tabData['module_list_datas'])) { + foreach ($tabData['module_list_datas'] as $module) { + if (isset($module['module_datas'])) { + foreach ($module['module_datas'] as $moduleData) { + // 获取tab信息 + $moduleParams = $moduleData['module_params'] ?? []; + $tabsJson = $moduleParams['tabs'] ?? '[]'; + $tabs = json_decode($tabsJson, true) ?: []; + + // 处理每个tab的剧集 + foreach ($tabs as $tab) { + $tabContext = $tab['page_context'] ?? ''; + if (!empty($tabContext)) { + $tabEpisodes = $this->getEpisodesByTabContext($videoId, $tabContext); + $allEpisodes = array_merge($allEpisodes, $tabEpisodes); + } + } + + // 同时获取当前tab的剧集 + if (isset($moduleData['item_data_lists']['item_datas'])) { + foreach ($moduleData['item_data_lists']['item_datas'] as $item) { + $itemId = $item['item_id'] ?? ''; + $itemParams = $item['item_params'] ?? []; + + if (!empty($itemId)) { + $title = $itemParams['union_title'] ?? $itemParams['title'] ?? "第" . ($itemParams['order'] ?? '?') . "集"; + $allEpisodes[] = "{$title}\${$videoId}@{$itemId}"; + } + } + } + } + } + } + } + + return $allEpisodes; + } + + /** + * 获取指定tab上下文的所有剧集 + */ + private function getEpisodesByTabContext($videoId, $pageContext) { + $episodes = []; + + $url = $this->apiHost . '/trpc.universal_backend_service.page_server_rpc.PageServer/GetPageData'; + + $body = [ + "page_params" => [ + "req_from" => "web_vsite", + "page_id" => "vsite_episode_list", + "page_type" => "detail_operation", + "id_type" => "1", + "cid" => $videoId, + "vid" => "", + "lid" => "", + "page_context" => $pageContext, + "detail_page_type" => "1" + ], + "has_cache" => 1 + ]; + + $response = $this->fetch($url . '?video_appid=3000010&vplatform=2&vversion_name=8.2.96', [ + CURLOPT_POST => true, + CURLOPT_POSTFIELDS => json_encode($body), + CURLOPT_HTTPHEADER => [ + 'Content-Type: application/json', + 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', + 'Referer: ' . $this->host . '/', + 'Origin: ' . $this->host + ] + ]); + + $json = json_decode($response, true); + $data = $json['data'] ?? []; + + if (isset($data['module_list_datas'])) { + foreach ($data['module_list_datas'] as $module) { + if (isset($module['module_datas'])) { + foreach ($module['module_datas'] as $moduleData) { + if (isset($moduleData['item_data_lists']['item_datas'])) { + foreach ($moduleData['item_data_lists']['item_datas'] as $item) { + $itemId = $item['item_id'] ?? ''; + $itemParams = $item['item_params'] ?? []; + + if (!empty($itemId)) { + $title = $itemParams['union_title'] ?? $itemParams['title'] ?? "第" . ($itemParams['order'] ?? '?') . "集"; + $episodes[] = "{$title}\${$videoId}@{$itemId}"; + } + } + } + } + } + } + } + + return $episodes; + } +} + +(new Spider())->run(); \ No newline at end of file diff --git "a/spider/php/\350\215\220\347\211\207\345\275\261\350\247\206 \341\265\210\341\266\273.php" "b/spider/php/\350\215\220\347\211\207\345\275\261\350\247\206 \341\265\210\341\266\273.php" new file mode 100644 index 00000000..94c5c02c --- /dev/null +++ "b/spider/php/\350\215\220\347\211\207\345\275\261\350\247\206 \341\265\210\341\266\273.php" @@ -0,0 +1,346 @@ +<?php +require_once __DIR__ . '/lib/spider.php'; + +class Spider extends BaseSpider { + private $HOST; + private $HEADERS; + private $IMGHOST; + + public function init($extend = '') { + $this->HOST = 'https://api.ztcgi.com'; + $this->HEADERS = ['User-Agent: Mozilla/5.0 (Linux; Android 9; V2196A Build/PQ3A.190705.08211809; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/91.0.4472.114 Mobile Safari/537.36']; + $this->IMGHOST = 'https://img1.vbwus.com'; + $this->preprocess(); + } + + private function preprocess() { + try { + $res = json_decode($this->fetch($this->HOST . '/api/appAuthConfig', [], $this->HEADERS), true); + if (isset($res['data']['imgDomain'])) { + $this->IMGHOST = 'https://' . $res['data']['imgDomain']; + } + } catch (Exception $e) { + // 忽略错误 + } + } + + public function homeContent($filter) { + // 生成分类列表 + $classNames = explode('&', '电影&电视剧&动漫&短剧&综艺'); + $classUrls = explode('&', '1&2&3&67&4'); + $classes = []; + $filterObj = []; + + // 过滤器配置 + $filterConfig = [ + "1" => [ + ["key"=>"cateId","name"=>"分类","value"=>[["n"=>"全部","v"=>"1"],["n"=>"首推","v"=>"5"],["n"=>"动作","v"=>"6"],["n"=>"喜剧","v"=>"7"],["n"=>"战争","v"=>"8"],["n"=>"恐怖","v"=>"9"],["n"=>"剧情","v"=>"10"],["n"=>"爱情","v"=>"11"],["n"=>"科幻","v"=>"12"],["n"=>"动画","v"=>"13"]]], + ["key"=>"area","name"=>"地區","value"=>[["n"=>"全部","v"=>"0"],["n"=>"国产","v"=>"1"],["n"=>"中国香港","v"=>"3"],["n"=>"中国台湾","v"=>"6"],["n"=>"美国","v"=>"5"],["n"=>"韩国","v"=>"18"],["n"=>"日本","v"=>"2"]]], + ["key"=>"year","name"=>"年代","value"=>[["n"=>"全部","v"=>"0"],["n"=>"2025","v"=>"107"],["n"=>"2024","v"=>"119"],["n"=>"2023","v"=>"153"],["n"=>"2022","v"=>"101"],["n"=>"2021","v"=>"118"],["n"=>"2020","v"=>"16"],["n"=>"2019","v"=>"7"],["n"=>"2018","v"=>"2"],["n"=>"2017","v"=>"3"],["n"=>"2016","v"=>"22"]]], + ["key"=>"sort","name"=>"排序","value"=>[["n"=>"热门","v"=>"hot"],["n"=>"评分","v"=>"rating"],["n"=>"更新","v"=>"update"]]] + ], + "2" => [ + ["key"=>"cateId","name"=>"分类","value"=>[["n"=>"全部","v"=>"2"],["n"=>"首推","v"=>"14"],["n"=>"国产","v"=>"15"],["n"=>"港台","v"=>"16"],["n"=>"日韩","v"=>"17"],["n"=>"海外","v"=>"18"]]], + ["key"=>"area","name"=>"地區","value"=>[["n"=>"全部","v"=>"0"],["n"=>"国产","v"=>"1"],["n"=>"中国香港","v"=>"3"],["n"=>"中国台湾","v"=>"6"],["n"=>"美国","v"=>"5"],["n"=>"韩国","v"=>"18"],["n"=>"日本","v"=>"2"]]], + ["key"=>"year","name"=>"年代","value"=>[["n"=>"全部","v"=>"0"],["n"=>"2025","v"=>"107"],["n"=>"2024","v"=>"119"],["n"=>"2023","v"=>"153"],["n"=>"2022","v"=>"101"],["n"=>"2021","v"=>"118"],["n"=>"2020","v"=>"16"],["n"=>"2019","v"=>"7"],["n"=>"2018","v"=>"2"],["n"=>"2017","v"=>"3"],["n"=>"2016","v"=>"22"]]], + ["key"=>"sort","name"=>"排序","value"=>[["n"=>"热门","v"=>"hot"],["n"=>"评分","v"=>"rating"],["n"=>"更新","v"=>"update"]]] + ], + "3" => [ + ["key"=>"cateId","name"=>"分类","value"=>[["n"=>"全部","v"=>"3"],["n"=>"首推","v"=>"19"],["n"=>"海外","v"=>"20"],["n"=>"日本","v"=>"21"],["n"=>"国产","v"=>"22"]]], + ["key"=>"area","name"=>"地區","value"=>[["n"=>"全部","v"=>"0"],["n"=>"国产","v"=>"1"],["n"=>"中国香港","v"=>"3"],["n"=>"中国台湾","v"=>"6"],["n"=>"美国","v"=>"5"],["n"=>"韩国","v"=>"18"],["n"=>"日本","v"=>"2"]]], + ["key"=>"year","name"=>"年代","value"=>[["n"=>"全部","v"=>"0"],["n"=>"2025","v"=>"107"],["n"=>"2024","v"=>"119"],["n"=>"2023","v"=>"153"],["n"=>"2022","v"=>"101"],["n"=>"2021","v"=>"118"],["n"=>"2020","v"=>"16"],["n"=>"2019","v"=>"7"],["n"=>"2018","v"=>"2"],["n"=>"2017","v"=>"3"],["n"=>"2016","v"=>"22"]]], + ["key"=>"sort","name"=>"排序","value"=>[["n"=>"热门","v"=>"hot"],["n"=>"评分","v"=>"rating"],["n"=>"更新","v"=>"update"]]] + ], + "4" => [ + ["key"=>"cateId","name"=>"分类","value"=>[["n"=>"全部","v"=>"4"],["n"=>"首推","v"=>"23"],["n"=>"国产","v"=>"24"],["n"=>"海外","v"=>"25"],["n"=>"港台","v"=>"26"]]], + ["key"=>"area","name"=>"地區","value"=>[["n"=>"全部","v"=>"0"],["n"=>"国产","v"=>"1"],["n"=>"中国香港","v"=>"3"],["n"=>"中国台湾","v"=>"6"],["n"=>"美国","v"=>"5"],["n"=>"韩国","v"=>"18"],["n"=>"日本","v"=>"2"]]], + ["key"=>"year","name"=>"年代","value"=>[["n"=>"全部","v"=>"0"],["n"=>"2025","v"=>"107"],["n"=>"2024","v"=>"119"],["n"=>"2023","v"=>"153"],["n"=>"2022","v"=>"101"],["n"=>"2021","v"=>"118"],["n"=>"2020","v"=>"16"],["n"=>"2019","v"=>"7"],["n"=>"2018","v"=>"2"],["n"=>"2017","v"=>"3"],["n"=>"2016","v"=>"22"]]], + ["key"=>"sort","name"=>"排序","value"=>[["n"=>"热门","v"=>"hot"],["n"=>"评分","v"=>"rating"],["n"=>"更新","v"=>"update"]]] + ], + "67" => [ + ["key"=>"cateId","name"=>"分类","value"=>[["n"=>"全部","v"=>"67"],["n"=>"言情","v"=>"70"],["n"=>"爱情","v"=>"71"],["n"=>"战神","v"=>"72"],["n"=>"古代","v"=>"73"],["n"=>"萌娃","v"=>"74"],["n"=>"神医","v"=>"75"],["n"=>"玄幻","v"=>"76"],["n"=>"重生","v"=>"77"],["n"=>"激情","v"=>"79"],["n"=>"时尚","v"=>"82"],["n"=>"剧情演绎","v"=>"83"],["n"=>"影视","v"=>"84"],["n"=>"人文社科","v"=>"85"],["n"=>"二次元","v"=>"86"],["n"=>"明星八卦","v"=>"87"],["n"=>"随拍","v"=>"88"],["n"=>"个人管理","v"=>"89"],["n"=>"音乐","v"=>"90"],["n"=>"汽车","v"=>"91"],["n"=>"休闲","v"=>"92"],["n"=>"校园教育","v"=>"93"],["n"=>"游戏","v"=>"94"],["n"=>"科普","v"=>"95"],["n"=>"科技","v"=>"96"],["n"=>"时政社会","v"=>"97"],["n"=>"萌宠","v"=>"98"],["n"=>"体育","v"=>"99"],["n"=>"穿越","v"=>"80"],["n"=>"","v"=>"81"],["n"=>"闪婚","v"=>"112"]]], + ["key"=>"sort","name"=>"排序","value"=>[["n"=>"全部","v"=>""],["n"=>"最新","v"=>"update"],["n"=>"最热","v"=>"hot"]]] + ] + ]; + + for ($i = 0; $i < count($classNames); $i++) { + $typeId = $classUrls[$i]; + $classes[] = [ + 'type_id' => $typeId, + 'type_name' => $classNames[$i] + ]; + + if (isset($filterConfig[$typeId])) { + $filterObj[$typeId] = $filterConfig[$typeId]; + } + } + + // 获取首页推荐 (保持原有逻辑) + $homeUrl = $this->HOST . '/api/dyTag/hand_data?category_id=88'; + $homeData = json_decode($this->fetch($homeUrl, [], $this->HEADERS), true); + $list = []; + + if (isset($homeData['data']['20'])) { + foreach ($homeData['data']['20'] as $item) { + $list[] = [ + 'vod_id' => $item['id'], + 'vod_name' => $item['title'], + 'vod_pic' => $this->IMGHOST . $item['path'], + 'vod_remarks' => $item['mask'] . ' ⭐' . $item['score'] + ]; + } + } + + return [ + 'class' => $classes, + 'filters' => $filterObj, + 'list' => $list + ]; + } + + public function categoryContent($tid, $pg = 1, $filter = [], $extend = []) { + // 构建请求URL + $url = $this->HOST . '/api/crumb/list?page=' . $pg . '&type=0&limit=24'; + + // 处理过滤器 + $filterUrl = 'area=' . ($filter['area'] ?? '0') . '&sort=' . ($filter['sort'] ?? 'update') . '&year=' . ($filter['year'] ?? '0') . '&category_id=' . ($filter['cateId'] ?? $tid); + $url .= '&' . $filterUrl; + + // 处理短剧特殊情况 + if ($tid == 67) { + $url = str_replace('/api/crumb/list', '/api/crumb/shortList', $url); + } + + $data = json_decode($this->fetch($url, [], $this->HEADERS), true); + $list = []; + $total = 0; + + if (isset($data['data'])) { + // 检查API响应中是否包含total信息 + if (is_array($data['data']) && count($data['data']) > 0) { + // 对于API没有直接返回total的情况,我们假设总共有大量数据 + // 这里使用一个较大的值来确保分页正常工作 + $total = 1000; + + foreach ($data['data'] as $item) { + $isShort = $tid == 67; + $imgUrl = $this->IMGHOST . ($isShort ? ($item['cover_image'] ?? $item['path']) : ($item['thumbnail'] ?? $item['path'])); + + // 短剧需要在vod_id中附加类型信息,以便detailContent方法识别 + $vodId = $isShort ? ($item['id'] . '@67') : $item['id']; + + $list[] = [ + 'vod_id' => $vodId, + 'vod_name' => $item['title'], + 'vod_pic' => $imgUrl, + 'vod_remarks' => ($item['mask'] ?? '') . ' ⭐' . ($item['score'] ?? '0') + ]; + } + } + } + + return $this->pageResult($list, $pg, $total, 24); + } + + public function detailContent($ids) { + $id = is_array($ids) ? $ids[0] : $ids; + $tid = ''; + if (strpos($id, '@') !== false) { + list($id, $tid) = explode('@', $id); + } + + $isShort = $tid == 67; + $detailPath = $isShort ? '/api/detail?vid=' . $id : '/api/video/detailv2?id=' . $id; + + $detailUrl = $this->HOST . $detailPath; + $data = json_decode($this->fetch($detailUrl, [], $this->HEADERS), true); + $item = $data['data']; + + $playFrom = []; + $playUrls = []; + + if ($isShort) { + // 短剧可能有不同的数据结构,尝试多种方式获取播放列表 + $playlist = []; + + // 尝试从playlist字段获取 + if (isset($item['playlist']) && is_array($item['playlist'])) { + $playlist = $item['playlist']; + } + // 尝试从video_list字段获取 + elseif (isset($item['video_list']) && is_array($item['video_list'])) { + $playlist = $item['video_list']; + } + // 尝试从episodes字段获取 + elseif (isset($item['episodes']) && is_array($item['episodes'])) { + $playlist = $item['episodes']; + } + + if (count($playlist) > 0) { + $playFrom[] = '短剧'; + $urls = []; + foreach ($playlist as $ep) { + // 处理不同的数据结构 + $title = $ep['title'] ?? $ep['episode_title'] ?? ($ep['episode'] ?? '第1集'); + $url = $ep['url'] ?? $ep['video_url'] ?? $ep['play_url'] ?? ''; + + // 过滤无效地址和ftp协议 + if (!empty($url) && stripos($url, 'ftp://') !== 0) { + $urls[] = $title . '$' . $url; + } + } + if (!empty($urls)) { + $playUrls[] = implode('#', $urls); + } + } else { + // 尝试直接从item中获取单个播放地址(针对单集短剧) + $url = $item['url'] ?? $item['video_url'] ?? $item['play_url'] ?? ''; + if (!empty($url) && stripos($url, 'ftp://') !== 0) { + $playFrom[] = '短剧'; + $playUrls[] = '全集$' . $url; + } + } + } else { + if (isset($item['source_list_source'])) { + foreach ($item['source_list_source'] as $src) { + $name = $src['name'] == '常规线路' ? '边下边播线路' : $src['name']; + + $urls = []; + foreach ($src['source_list'] as $ep) { + $url = $ep['url']; + // 过滤ftp协议的地址,只保留http/https协议 + if (stripos($url, 'ftp://') === 0) { + continue; + } + $urls[] = ($ep['source_name'] ?? $ep['weight']) . '$' . $url; + } + if (!empty($urls)) { + $playFrom[] = $name; + $playUrls[] = implode('#', $urls); + } + } + } + + // 尝试从source_list字段获取(备用方案) + if (empty($playUrls) && isset($item['source_list'])) { + foreach ($item['source_list'] as $src) { + $name = $src['name'] ?? '默认线路'; + + $urls = []; + foreach ($src['source'] as $ep) { + $url = $ep['url']; + if (stripos($url, 'ftp://') !== 0) { + $urls[] = ($ep['name'] ?? $ep['title']) . '$' . $url; + } + } + if (!empty($urls)) { + $playFrom[] = $name; + $playUrls[] = implode('#', $urls); + } + } + } + } + + return [ + 'list' => [[ + 'vod_id' => $id, + 'vod_name' => $item['title'], + 'vod_pic' => $this->IMGHOST . ($isShort ? ($item['cover_image'] ?? $item['path']) : ($item['thumbnail'] ?? $item['path'])), + 'vod_year' => $item['year'] ?? '', + 'vod_area' => $item['area'] ?? '', + 'vod_remarks' => $item['update_cycle'] ?? $item['mask'] ?? '', + 'vod_actor' => implode('/', array_column($item['actors'] ?? [], 'name')), + 'vod_director' => implode('/', array_column($item['directors'] ?? [], 'name')), + 'vod_content' => $item['description'] ?? '', + 'vod_play_from' => implode('$$$', $playFrom), + 'vod_play_url' => implode('$$$', $playUrls) + ]] + ]; + } + + public function searchContent($key, $quick = false, $pg = 1) { + $searchUrl = $this->HOST . '/api/v2/search/videoV2?key=' . urlencode($key) . '&page=' . $pg; + $data = json_decode($this->fetch($searchUrl, [], $this->HEADERS), true); + $list = []; + $total = 0; + + if (isset($data['data'])) { + if (is_array($data['data']) && count($data['data']) > 0) { + // 对于搜索结果,同样使用较大值确保分页正常 + $total = 1000; + + foreach ($data['data'] as $item) { + // 检查是否为短剧,根据category_id判断 + $isShort = isset($item['category_id']) && $item['category_id'] == 67; + $vodId = $isShort ? ($item['id'] . '@67') : $item['id']; + + $list[] = [ + 'vod_id' => $vodId, + 'vod_name' => $item['title'], + 'vod_pic' => $this->IMGHOST . $item['thumbnail'], + 'vod_remarks' => $item['mask'] . ' ⭐' . $item['score'] + ]; + } + } + } + + return $this->pageResult($list, $pg, $total, 20); + } + + public function playerContent($flag, $id, $vipFlags = []) { + try { + // 优化视频播放,使用更高效的处理方式 + // 检查是否为m3u8格式(流媒体格式) + if (stripos($id, '.m3u8') !== false) { + return [ + 'parse' => 0, + 'url' => $id, + 'header' => [ + 'User-Agent' => 'Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Mobile/15E148 Safari/604.1', + 'Referer' => $id, + 'Accept' => '*/*', + 'Connection' => 'keep-alive', + 'Origin' => parse_url($id, PHP_URL_SCHEME) . '://' . parse_url($id, PHP_URL_HOST) + ] + ]; + } + + // 检查是否为mp4等直接视频格式 + $videoExtensions = ['mp4', 'flv', 'avi', 'wmv', 'mov', 'webm']; + $path = parse_url($id, PHP_URL_PATH) ?? ''; + $extension = strtolower(pathinfo($path, PATHINFO_EXTENSION)); + + if (in_array($extension, $videoExtensions)) { + return [ + 'parse' => 0, + 'url' => $id, + 'header' => [ + 'User-Agent' => 'Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Mobile/15E148 Safari/604.1', + 'Referer' => $id, + 'Range' => 'bytes=0-', // 支持断点续传 + 'Accept-Ranges' => 'bytes' + ] + ]; + } + + // 默认返回原始地址 + return [ + 'parse' => 0, + 'url' => $id, + 'header' => [ + 'User-Agent' => 'Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Mobile/15E148 Safari/604.1', + 'Referer' => $id + ] + ]; + } catch (Exception $e) { + // 发生错误时返回原始地址 + return [ + 'parse' => 0, + 'url' => $id, + 'header' => ['User-Agent' => 'Mozilla/5.0'] + ]; + } + } +} + +(new Spider())->run(); \ No newline at end of file diff --git "a/spider/php/\351\205\267\347\210\261\346\274\253\347\224\273 \341\265\210\341\266\273[\347\224\273].php" "b/spider/php/\351\205\267\347\210\261\346\274\253\347\224\273 \341\265\210\341\266\273[\347\224\273].php" new file mode 100644 index 00000000..851099ce --- /dev/null +++ "b/spider/php/\351\205\267\347\210\261\346\274\253\347\224\273 \341\265\210\341\266\273[\347\224\273].php" @@ -0,0 +1,276 @@ +<?php +require_once __DIR__ . '/lib/spider.php'; + +class Spider extends BaseSpider { + + public function getName() { + return "酷爱漫画"; + } + + public function init($extend = "") { + // pass + } + + public function isVideoFormat($url) { + return false; + } + + public function manualVideoCheck() { + return false; + } + + private function getHeader() { + return [ + "User-Agent" => "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.203 Safari/537.36", + "Referer" => "https://www.kuimh.com/" + ]; + } + + public function homeContent($filter) { + $classes = [ + ["type_name" => "国产", "type_id" => "1"], + ["type_name" => "日本", "type_id" => "2"], + ["type_name" => "韩国", "type_id" => "3"], + ["type_name" => "欧美", "type_id" => "5"], + ["type_name" => "其他", "type_id" => "7"], + ["type_name" => "日韩", "type_id" => "8"] + ]; + + $tags = ["全部", "恋爱", "古风", "校园", "奇幻", "大女主", "治愈", "穿越", "励志", "爆笑", "萌系", "玄幻", "日常", "都市", "彩虹", "灵异", "悬疑", "少年"]; + $tagValues = []; + foreach ($tags as $t) { + $tagValues[] = ["n" => $t, "v" => $t]; + } + $filterConfig = [ + "key" => "tag", + "name" => "题材", + "value" => $tagValues + ]; + + $statusConfig = [ + "key" => "end", + "name" => "状态", + "value" => [ + ["n" => "全部", "v" => "-1"], + ["n" => "连载", "v" => "0"], + ["n" => "完结", "v" => "1"] + ] + ]; + + $filters = []; + foreach ($classes as $c) { + $filters[$c['type_id']] = [$filterConfig, $statusConfig]; + } + + return ["class" => $classes, "filters" => $filters]; + } + + public function homeVideoContent() { + return $this->categoryContent("1", 1, [], []); + } + + public function categoryContent($tid, $pg = 1, $filter = [], $extend = []) { + $tag = urlencode($extend['tag'] ?? '全部'); + $end = $extend['end'] ?? '-1'; + $url = "https://www.kuimh.com/booklist?tag={$tag}&area={$tid}&end={$end}&page={$pg}"; + + try { + $html = $this->fetch($url, ['headers' => $this->getHeader()]); + $items = $this->pdfa($html, '.mh-item'); + + $videos = []; + foreach ($items as $item) { + $vid = $this->pd($item, 'a&&href'); + $style = $this->pd($item, 'p&&style'); + $cover = ""; + if (preg_match('/url\((.*?)\)/', $style, $matches)) { + $cover = $matches[1]; + } + + // 尝试提取名称,Python逻辑是取第二个a标签,这里简化 + $name = $this->pd($item, 'a:eq(1)&&Text'); + if (!$name) { + $name = $this->pd($item, '.title a&&Text'); + } + if (!$name) { + $name = $this->pd($item, 'a&&title'); + } + if (!$name) { + $name = $this->pd($item, 'Text'); + } + + $videos[] = [ + "vod_id" => $vid, + "vod_name" => trim($name), + "vod_pic" => $cover, + "vod_remarks" => "" + ]; + } + + return [ + "list" => $videos, + "page" => $pg, + "pagecount" => 9999, + "limit" => 30, + "total" => 999999 + ]; + } catch (Exception $e) { + return ["list" => []]; + } + } + + public function detailContent($ids) { + $vid = $ids[0]; + $url = (strpos($vid, 'http') === 0) ? $vid : "https://www.kuimh.com{$vid}"; + + try { + $html = $this->fetch($url, ['headers' => $this->getHeader()]); + + $name = $this->pd($html, '.info h1&&Text'); + $cover = $this->pd($html, '.cover img&&src'); + $desc = $this->pd($html, '.content p&&Text'); + + $chapterList = $this->pdfa($html, '.mCustomScrollBox li a'); + if (empty($chapterList)) { + $chapterList = $this->pdfa($html, '#detail-list-select li a'); + } + + $vodPlayUrlList = []; + foreach ($chapterList as $chapter) { + $chapterName = $this->pd($chapter, 'a&&Text'); + $chapterHref = $this->pd($chapter, 'a&&href'); + + if (!$chapterHref) continue; + + $vodPlayUrlList[] = "{$chapterName}\${$chapterHref}"; + } + + $playUrlStr = implode("#", $vodPlayUrlList); + + return [ + "list" => [[ + "vod_id" => $vid, + "vod_name" => $name, + "vod_pic" => $cover, + "type_name" => "漫画", + "vod_year" => "", + "vod_area" => "", + "vod_remarks" => "", + "vod_actor" => "", + "vod_director" => "", + "vod_content" => $desc, + "vod_play_from" => '阅读', + "vod_play_url" => $playUrlStr + ]] + ]; + } catch (Exception $e) { + return ["list" => []]; + } + } + + public function searchContent($key, $quick = false, $pg = 1) { + $key = urlencode($key); + $url = "https://www.kuimh.com/search?keyword={$key}&page={$pg}"; + + try { + $html = $this->fetch($url, ['headers' => $this->getHeader()]); + $items = $this->pdfa($html, '.mh-item'); + + $videos = []; + foreach ($items as $item) { + $vid = $this->pd($item, 'a&&href'); + $style = $this->pd($item, 'p&&style'); + $cover = ""; + if (preg_match('/url\((.*?)\)/', $style, $matches)) { + $cover = $matches[1]; + } + + $name = $this->pd($item, '.title a&&title'); + if (!$name) $name = $this->pd($item, 'a&&title'); + if (!$name) $name = $this->pd($item, 'Text'); + + $videos[] = [ + "vod_id" => $vid, + "vod_name" => trim($name), + "vod_pic" => $cover, + "vod_remarks" => "" + ]; + } + return ['list' => $videos]; + } catch (Exception $e) { + return ['list' => []]; + } + } + + public function playerContent($flag, $id, $vipFlags = []) { + $url = (strpos($id, 'http') === 0) ? $id : "https://www.kuimh.com{$id}"; + $headers = $this->getHeader(); + $headers['Referer'] = $url; + + try { + $html = $this->fetch($url, ['headers' => $headers]); + + $imageList = []; + + // 1. DOM 解析 + $imgs = $this->pdfa($html, '.comicpage img'); + if (empty($imgs)) { + $imgs = $this->pdfa($html, '.comiclist img'); + } + + foreach ($imgs as $img) { + $src = $this->pd($img, 'data-echo'); + if (!$src) $src = $this->pd($img, 'data-src'); + if (!$src) $src = $this->pd($img, 'data-original'); + if (!$src) $src = $this->pd($img, 'src'); + + if ($src) $imageList[] = $src; + } + + // 2. data-echo 全局查找 + if (empty($imageList)) { + $allLazyImgs = $this->pdfa($html, 'img[data-echo]'); + foreach ($allLazyImgs as $img) { + $src = $this->pd($img, 'data-echo'); + if ($src && !in_array($src, $imageList)) { + $imageList[] = $src; + } + } + } + + // 3. 正则兜底 + if (empty($imageList)) { + if (preg_match_all('/(https?:\/\/[^"\'\\\\]+\.(?:jpg|png|jpeg|webp))/', $html, $matches)) { + foreach ($matches[1] as $m) { + $imageList[] = $m; + } + } + } + + // 4. 过滤与去重 + $uniqueImages = []; + foreach ($imageList as $i) { + if (in_array($i, $uniqueImages)) continue; + if (strpos($i, "grey.gif") !== false) continue; + if (strpos($i, "logo") !== false) continue; + if (strpos($i, "icon") !== false) continue; + if (strpos($i, "tu.petatt.cn") !== false) continue; + + $uniqueImages[] = $i; + } + + $novelData = implode("&&", $uniqueImages); + + return [ + "parse" => 0, + "playUrl" => "", + "url" => "pics://{$novelData}", + "header" => "" + ]; + } catch (Exception $e) { + return ["parse" => 0, "url" => "", "header" => ""]; + } + } +} + +(new Spider())->run(); diff --git "a/spider/php/\351\207\221\347\211\214 \341\265\210\341\266\273.php" "b/spider/php/\351\207\221\347\211\214 \341\265\210\341\266\273.php" new file mode 100644 index 00000000..f8e77111 --- /dev/null +++ "b/spider/php/\351\207\221\347\211\214 \341\265\210\341\266\273.php" @@ -0,0 +1,228 @@ +<?php +require_once __DIR__ . '/lib/spider.php'; + +class Spider extends BaseSpider { + private $HOST = 'https://m.jiabaide.cn'; + private $UA = 'Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.91 Mobile Safari/537.36'; + + /** + * 核心签名算法:sha1(md5(query_string)) + */ + private function getSignedHeaders($params) { + $t = (string)(time() * 1000); // 毫秒时间戳 + $params['key'] = 'cb808529bae6b6be45ecfab29a4889bc'; + $params['t'] = $t; + + // 构建 QueryString + $query = []; + foreach ($params as $k => $v) { + $query[] = "$k=$v"; + } + $queryStr = implode('&', $query); + + // 签名逻辑:SHA1(MD5(str)) + $sign = sha1(md5($queryStr)); + + return [ + 'User-Agent: ' . $this->UA, + 'Referer: ' . $this->HOST, + 't: ' . $t, + 'sign: ' . $sign + ]; + } + + public function homeContent($filter) { + // 5. 首页 (获取分类与筛选) + $typeUrl = $this->HOST . '/api/mw-movie/anonymous/get/filer/type'; + $typeRes = $this->fetch($typeUrl, [], $this->getSignedHeaders([])); + $typeArr = json_decode($typeRes, true)['data'] ?? []; + + $classes = []; + foreach ($typeArr as $item) { + $classes[] = ['type_id' => (string)$item['typeId'], 'type_name' => $item['typeName']]; + } + + // 获取筛选 + $filterUrl = $this->HOST . '/api/mw-movie/anonymous/v1/get/filer/list'; + $filterRes = $this->fetch($filterUrl, [], $this->getSignedHeaders([])); + $filterData = json_decode($filterRes, true)['data'] ?? []; + + $filters = []; + $nameMap = [ + 'typeList' => ['key' => 'type', 'name' => '类型'], + 'plotList' => ['key' => 'class', 'name' => '剧情'], + 'districtList' => ['key' => 'area', 'name' => '地区'], + 'languageList' => ['key' => 'lang', 'name' => '语言'], + 'yearList' => ['key' => 'year', 'name' => '年份'] + ]; + + foreach ($classes as $cls) { + $tid = $cls['type_id']; + $fRow = []; + foreach ($nameMap as $apiKey => $cfg) { + if (!isset($filterData[$tid][$apiKey])) continue; + $values = [['n' => '全部', 'v' => '']]; + foreach ($filterData[$tid][$apiKey] as $v) { + $values[] = [ + 'n' => $v['itemText'], + 'v' => ($apiKey === 'typeList') ? $v['itemValue'] : $v['itemText'] + ]; + } + $fRow[] = ['key' => $cfg['key'], 'name' => $cfg['name'], 'value' => $values]; + } + // 增加排序 + $fRow[] = [ + 'key' => 'by', 'name' => '排序', + 'value' => [ + ['n' => '最近更新', 'v' => '1'], + ['n' => '添加时间', 'v' => '2'], + ['n' => '人气高低', 'v' => '3'], + ['n' => '评分高低', 'v' => '4'] + ] + ]; + $filters[$tid] = $fRow; + } + + // 首页推荐 + $hotUrl = $this->HOST . '/api/mw-movie/anonymous/home/hotSearch'; + $hotRes = $this->fetch($hotUrl, [], $this->getSignedHeaders([])); + $hotVods = json_decode($hotRes, true)['data'] ?? []; + $list = []; + foreach (array_slice($hotVods, 0, 20) as $it) { + $list[] = [ + 'vod_id' => $it['vodId'], + 'vod_name' => $it['vodName'], + 'vod_pic' => $it['vodPic'], + 'vod_remarks' => $it['vodRemarks'] + ]; + } + + return [ + 'class' => $classes, + 'filters' => $filters, + 'list' => $list + ]; + } + + public function categoryContent($tid, $pg = 1, $filter = [], $extend = []) { + $params = [ + 'area' => $extend['area'] ?? '', + 'lang' => $extend['lang'] ?? '', + 'pageNum' => $pg, + 'pageSize' => '30', + 'sort' => $extend['by'] ?? '1', + 'sortBy' => '1', + 'type' => $extend['type'] ?? '', + 'type1' => $tid, + 'v_class' => $extend['class'] ?? '', + 'year' => $extend['year'] ?? '', + ]; + + $apiUrl = $this->HOST . '/api/mw-movie/anonymous/video/list?' . http_build_query($params); + $res = $this->fetch($apiUrl, [], $this->getSignedHeaders($params)); + $json = json_decode($res, true); + + $list = []; + if (isset($json['data']['list'])) { + foreach ($json['data']['list'] as $it) { + $list[] = [ + 'vod_id' => $it['vodId'], + 'vod_name' => $it['vodName'], + 'vod_pic' => $it['vodPic'], + 'vod_remarks' => $it['vodRemarks'] . '_' . $it['vodDoubanScore'] + ]; + } + } + + $total = $json['data']['total'] ?? 0; + return $this->pageResult($list, $pg, $total, 30); + } + + public function detailContent($ids) { + $id = is_array($ids) ? $ids[0] : $ids; + $params = ['id' => $id]; + $apiUrl = $this->HOST . '/api/mw-movie/anonymous/video/detail?' . http_build_query($params); + $res = $this->fetch($apiUrl, [], $this->getSignedHeaders($params)); + $json = json_decode($res, true); + $kvod = $json['data'] ?? null; + + if (!$kvod) { + return ['list' => []]; + } + + $episodes = []; + if (!empty($kvod['episodeList'])) { + foreach ($kvod['episodeList'] as $it) { + // 存入格式:名字$ID@NID + $episodes[] = $it['name'] . '$' . $kvod['vodId'] . '@' . $it['nid']; + } + } + + $vod = [ + 'vod_id' => $kvod['vodId'], + 'vod_name' => $kvod['vodName'], + 'vod_pic' => $kvod['vodPic'], + 'type_name' => $kvod['vodClass'], + 'vod_remarks' => $kvod['vodRemarks'], + 'vod_content' => trim(strip_tags($kvod['vodContent'] ?? '')), + 'vod_play_from' => '金牌线路', + 'vod_play_url' => implode('#', $episodes) + ]; + + return ['list' => [$vod]]; + } + + public function searchContent($key, $quick = false, $pg = 1) { + $page = max(1, intval($pg)); + $params = [ + 'keyword' => $key, + 'pageNum' => $pg, + 'pageSize' => '30' + ]; + $apiUrl = $this->HOST . '/api/mw-movie/anonymous/video/searchByWordPageable?' . http_build_query($params); + $res = $this->fetch($apiUrl, [], $this->getSignedHeaders($params)); + $json = json_decode($res, true); + + $list = []; + if (isset($json['data']['list'])) { + foreach ($json['data']['list'] as $it) { + $list[] = [ + 'vod_id' => $it['vodId'], + 'vod_name' => $it['vodName'], + 'vod_pic' => $it['vodPic'], + 'vod_remarks' => $it['vodRemarks'] + ]; + } + } + + $total = $json['data']['total'] ?? 0; + return $this->pageResult($list, $pg, $total, 30); + } + + public function playerContent($flag, $id, $vipFlags = []) { + // 格式: vodId@nid + list($sid, $nid) = explode('@', $id); + $params = [ + 'clientType' => '3', + 'id' => $sid, + 'nid' => $nid + ]; + $apiUrl = $this->HOST . '/api/mw-movie/anonymous/v2/video/episode/url?' . http_build_query($params); + $res = $this->fetch($apiUrl, [], $this->getSignedHeaders($params)); + $json = json_decode($res, true); + + $playUrl = ""; + if (!empty($json['data']['list'])) { + // 取第一个清晰度的 URL + $playUrl = $json['data']['list'][0]['url']; + } + + return [ + 'parse' => 0, + 'url' => $playUrl, + 'header' => ['User-Agent' => $this->UA] + ]; + } +} + +(new Spider())->run(); diff --git "a/spider/php/\351\230\205\350\257\273\345\212\251\346\211\213 \341\265\210\341\266\273[\344\271\246].php" "b/spider/php/\351\230\205\350\257\273\345\212\251\346\211\213 \341\265\210\341\266\273[\344\271\246].php" new file mode 100644 index 00000000..556845d8 --- /dev/null +++ "b/spider/php/\351\230\205\350\257\273\345\212\251\346\211\213 \341\265\210\341\266\273[\344\271\246].php" @@ -0,0 +1,324 @@ +<?php +require_once __DIR__ . '/lib/spider.php'; + +class Spider extends BaseSpider { + + private const AES_KEY = '242ccb8230d709e1'; + private const SIGN_KEY = 'd3dGiJc651gSQ8w1'; + private const APP_ID = 'com.kmxs.reader'; + + private const BASE_HEADERS = [ + "app-version" => "51110", + "platform" => "android", + "reg" => "0", + "AUTHORIZATION" => "", + "application-id" => self::APP_ID, + "net-env" => "1", + "channel" => "unknown", + "qm-params" => "" + ]; + + public function init($extend = "") { + parent::init($extend); + } + + public function getName() { + return "阅读助手"; + } + + private function getSign($params) { + ksort($params); + $signStr = ""; + foreach ($params as $k => $v) { + $signStr .= "{$k}={$v}"; + } + $signStr .= self::SIGN_KEY; + return md5($signStr); + } + + private function getHeaders($params) { + $headers = self::BASE_HEADERS; + $headers['sign'] = $this->getSign($params); + return $headers; + } + + private function getApiUrl($path, &$params, $domainType = "bc") { + $baseUrl = ($domainType == "bc") ? "https://api-bc.wtzw.com" : "https://api-ks.wtzw.com"; + if (strpos($path, "search") !== false) { + $baseUrl = "https://api-bc.wtzw.com"; + } + + // PHP headers logic is separate from URL params in fetch + // But the sign is calculated on params. + // And requests in Python sends params in query string. + // So we need to construct URL with query string. + // Also sign must be in headers. + + // Wait, Python code: + // params['sign'] = self.get_sign(params) -> This adds sign to params! + // headers['sign'] = self.get_sign(headers) -> This adds sign to headers (based on headers)! + + // Let's re-read Python code carefully. + /* + def get_sign(self, params): + # sorts params and md5 + + def get_headers(self): + headers = self.BASE_HEADERS.copy() + headers['sign'] = self.get_sign(headers) <-- Sign of HEADERS + return headers + + def get_api_url(self, path, params, domain_type="bc"): + params['sign'] = self.get_sign(params) <-- Sign of PARAMS + ... + return url, params + */ + + // So we have TWO signatures: one in params (signing params) and one in headers (signing headers). + + // Params signing + $params['sign'] = $this->getSign($params); + + // Build query string + $queryString = http_build_query($params); + + return "{$baseUrl}{$path}?{$queryString}"; + } + + private function getRequestHeaders() { + // Headers signing + $headers = self::BASE_HEADERS; + $headers['sign'] = $this->getSign($headers); + $headers['User-Agent'] = "okhttp/3.12.1"; + + // Format for fetch + // fetch expects array Key => Value + return $headers; + } + + private function decryptContent($base64Content) { + try { + $encryptedBytes = base64_decode($base64Content); + if (strlen($encryptedBytes) < 16) { + return "数据长度不足"; + } + + $iv = substr($encryptedBytes, 0, 16); + $ciphertext = substr($encryptedBytes, 16); + + // aes-128-cbc + $decrypted = openssl_decrypt($ciphertext, 'aes-128-cbc', self::AES_KEY, OPENSSL_RAW_DATA, $iv); + + if ($decrypted === false) { + return "解密失败"; + } + + return trim($decrypted); + } catch (Exception $e) { + return "解密错误: " . $e->getMessage(); + } + } + + public function homeContent($filter = []) { + $cats = [ + ["type_name" => "玄幻奇幻", "type_id" => "1|202"], + ["type_name" => "都市人生", "type_id" => "1|203"], + ["type_name" => "武侠仙侠", "type_id" => "1|205"], + ["type_name" => "历史军事", "type_id" => "1|56"], + ["type_name" => "科幻末世", "type_id" => "1|64"], + ["type_name" => "游戏竞技", "type_id" => "1|75"], + ["type_name" => "现代言情", "type_id" => "2|1"], + ["type_name" => "古代言情", "type_id" => "2|2"], + ["type_name" => "幻想言情", "type_id" => "2|4"], + ["type_name" => "婚恋情感", "type_id" => "2|6"], + ["type_name" => "悬疑推理", "type_id" => "3|262"] + ]; + return ['class' => $cats, 'filters' => (object)[]]; + } + + public function categoryContent($tid, $pg = 1, $filter = [], $extend = []) { + $parts = explode("|", $tid); + $gender = $parts[0] ?? "1"; + $catId = $parts[1] ?? "202"; + + $params = [ + 'gender' => $gender, + 'category_id' => $catId, + 'need_filters' => '1', + 'page' => $pg, + 'need_category' => '1' + ]; + + $url = $this->getApiUrl("/api/v4/category/get-list", $params, "bc"); + $headers = $this->getRequestHeaders(); + + try { + $json = $this->fetchJson($url, ['headers' => $headers]); + + $bookList = []; + if (isset($json['data']['books'])) { + $bookList = $json['data']['books']; + } elseif (isset($json['books'])) { + $bookList = $json['books']; + } + + $videos = []; + foreach ($bookList as $book) { + $pic = $book['image_link'] ?? ''; + if (strpos($pic, 'http://') === 0) { + $pic = str_replace('http://', 'https://', $pic); + } + + $videos[] = [ + "vod_id" => (string)($book['id'] ?? ''), + "vod_name" => $book['title'] ?? '', + "vod_pic" => $pic, + "vod_remarks" => $book['author'] ?? '' + ]; + } + + return ['list' => $videos, 'page' => $pg, 'pagecount' => 999, 'limit' => 20, 'total' => 9999]; + + } catch (Exception $e) { + return ['list' => []]; + } + } + + public function detailContent($ids) { + $bid = $ids[0]; + $headers = $this->getRequestHeaders(); + + $detailParams = ['id' => $bid, 'imei_ip' => '2937357107', 'teeny_mode' => '0']; + $detailUrl = $this->getApiUrl("/api/v4/book/detail", $detailParams, "bc"); + + $vod = ["vod_id" => $bid, "vod_name" => "获取中...", "vod_play_from" => "阅读助手"]; + + try { + $json = $this->fetchJson($detailUrl, ['headers' => $headers]); + + if (isset($json['data']['book'])) { + $bookInfo = $json['data']['book']; + $vod["vod_name"] = $bookInfo['title'] ?? ''; + + $pic = $bookInfo['image_link'] ?? ''; + if (strpos($pic, 'http://') === 0) { + $pic = str_replace('http://', 'https://', $pic); + } + $vod["vod_pic"] = $pic; + + $vod["type_name"] = $bookInfo['category_name'] ?? ''; + $vod["vod_remarks"] = ($bookInfo['words_num'] ?? '') . "字"; + $vod["vod_actor"] = $bookInfo['author'] ?? ''; + $vod["vod_content"] = $bookInfo['intro'] ?? ''; + } + + // Get Chapters + $chapterParams = ['id' => $bid]; + $chapterUrl = $this->getApiUrl("/api/v1/chapter/chapter-list", $chapterParams, "ks"); + + $jsonC = $this->fetchJson($chapterUrl, ['headers' => $headers]); + + $lists = []; + if (isset($jsonC['data']['chapter_lists'])) { + $lists = $jsonC['data']['chapter_lists']; + } + + $chapterList = []; + foreach ($lists as $item) { + $cid = (string)$item['id']; + $cname = str_replace(["@@", "$"], ["-", ""], $item['title']); + $urlCode = "{$bid}@@{$cid}@@{$cname}"; + $chapterList[] = "{$cname}\${$urlCode}"; + } + + $vod['vod_play_url'] = implode("#", $chapterList); + return ["list" => [$vod]]; + + } catch (Exception $e) { + $vod["vod_content"] = "Error: " . $e->getMessage(); + return ["list" => [$vod]]; + } + } + + public function searchContent($key, $quick = false, $pg = 1) { + $params = ['gender' => '3', 'imei_ip' => '2937357107', 'page' => $pg, 'wd' => $key]; + $url = $this->getApiUrl("/api/v5/search/words", $params, "bc"); + $headers = $this->getRequestHeaders(); + + try { + $json = $this->fetchJson($url, ['headers' => $headers]); + + $videos = []; + if (isset($json['data']['books'])) { + foreach ($json['data']['books'] as $book) { + $videos[] = [ + "vod_id" => (string)$book['id'], + "vod_name" => $book['original_title'], + "vod_pic" => $book['image_link'], + "vod_remarks" => $book['original_author'] + ]; + } + } + return ['list' => $videos, 'page' => $pg]; + } catch (Exception $e) { + return ['list' => [], 'page' => $pg]; + } + } + + public function playerContent($flag, $id, $vipFlags = []) { + try { + $parts = explode("@@", $id); + $bid = $parts[0]; + $cid = $parts[1]; + $title = isset($parts[2]) ? $parts[2] : ""; + + $params = ['id' => $bid, 'chapterId' => $cid]; + $url = $this->getApiUrl("/api/v1/chapter/content", $params, "ks"); + $headers = $this->getRequestHeaders(); + + $json = $this->fetchJson($url, ['headers' => $headers]); + + $content = ""; + if (isset($json['data']['content'])) { + if (!$title && isset($json['data']['title'])) { + $title = $json['data']['title']; + } + $content = $this->decryptContent($json['data']['content']); + } else { + $msg = $json['msg'] ?? '未知错误'; + $content = "加载失败: {$msg}"; + } + + if (!$title) $title = "章节正文"; + + $resultData = [ + 'title' => $title, + 'content' => $content + ]; + + $ret = json_encode($resultData, JSON_UNESCAPED_UNICODE); + $finalUrl = "novel://{$ret}"; + + return [ + "parse" => 0, + "playUrl" => "", + "url" => $finalUrl, + "header" => "" + ]; + + } catch (Exception $e) { + $errData = [ + 'title' => "错误", + 'content' => "发生异常: " . $e->getMessage() + ]; + return [ + "parse" => 0, + "playUrl" => "", + "url" => "novel://" . json_encode($errData, JSON_UNESCAPED_UNICODE), + "header" => "" + ]; + } + } +} + +(new Spider())->run(); diff --git "a/spider/php/\351\233\266\345\272\246\345\275\261\350\247\206 \341\265\210\341\266\273.php" "b/spider/php/\351\233\266\345\272\246\345\275\261\350\247\206 \341\265\210\341\266\273.php" new file mode 100644 index 00000000..ca733ef6 --- /dev/null +++ "b/spider/php/\351\233\266\345\272\246\345\275\261\350\247\206 \341\265\210\341\266\273.php" @@ -0,0 +1,384 @@ +<?php +require_once __DIR__ . '/lib/spider.php'; + +class Spider extends BaseSpider { + private $host = 'http://ldys.sq1005.top'; + private $publicKey = "-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCoYt0BP77U+DM08BiI/QbSRIfxijXo85BTPqIM1Ow8BNwhLETzRIZ+dEwdWDbydG/PspgBAfRpGaYVdJYtvaC2JnoO8+Ik6qMWojfEJxSFLa0Pb0A892tun4gsxoEMjcreZ+YGyaBxAfqX0BSMfdrOgIYaZQjYrw9TRLlUT31QoQIDAQAB\n-----END PUBLIC KEY-----"; + private $privateKey = "-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCquQQ5r6+yJI8CDFkXRp8vUsdD45ov8EP12ooLs56ca2DQXaSNGS9910bAPVA9chkp0mKIvKqjAsHz5Tl9EeNPblarGEeJUIxpxZtiSqNTpvtiD/TjhpzuHYic7RAfQ/h7p/ypE8ymU42pYjsB5t26Mv6XgkLV+jzrSf73HlCuS0iMyLmt6zz3Mw9izM13EpB8iFLtfbbYymycKTx4RAmPQLwhNGex/AlUIYxXP4R2yyaa4W6mEtc6aME2QuzJFxPgP3HJ9NBx/LWVn4skxWjZ7zg+VRQRHnjyVaSLu3Z5gN5ITWCyE32qaHJa6WBahZj5jWhRyAG1bQ+xKJa8lBL5AgMBAAECggEAUwv9SjJ0PSwbhNuM2w23kcWquROWhYtTA91zGY4esehqB/IFgb2mpIh8Gje5OKqwIu/8jpd4SiOlRYdUF8sD0DfUYRZGdj2AkFNX6tBz8tVfo6wvbB6naA1lzzBij1L5JO3qsjS3cJFkb+kg2yP66AC2Z+0tpfk8eRhdtshAZwfcd1DEGt1uAvYL1eaUK9HRvpt9lPeGcHERDl2hBd4uyaF0K1O+zF9y59nYbTySWPxRZq3sFEE85xRMlstD7YZi7W2gKvMFRD4/FKmrZ3m7aKJRITtyKOyyPcYmepNv3Qv7kk59Pg38n2WWQ0Ra/bCH3E48YNCnQvZMpitkTfJhoQKBgQDbnROOYTP8OTJ6f/qhoGjxeO3x1VOaOp8l0x7b0SCfoqNGS0Cyiqj72BmJtPMPqSTjn6MmNzqbg1KOdhXyzNozs+i5ccW1M56j96mr5I/Z0FpE3oyIHNfDDBlf9M8YQqEF9oYxniYYft9oapO7cRQkHER6qpvnHTavwlv4m78CXwKBgQDHAjs2YlpKDdI1lcbZJCc7TwtH+Pd2bUki8YXafWNcPhITQHbOZjr310eK1QJC6GJncjkOqbX7yv3ivvTO35FZTQhuA1xEG1P00FG8bE0tHYPIwQHi9y0eA5cieMdo8E6XYria1mw/3fqSQEsfZyJlR32JQIoGAipM8iO1X2nZpwKBgDkMFIhnt5lNQk+P7wsNIDWZtDWdtJnboHuy29E+Abt2A/O+mI/IdRz2hau/1WO8DFkUnszOi+rZshhPlGP90rCbi1igtTrcrdjp/KkqNjPea5R4OwkgdOu1uOG0NheXNzzVTQaWjk7Opjn5dWa7eP/oV+GFb/oZHJuLYVizHGsBAoGADA7rjZEKDYCm4w5PPSr+oY5ZjaPdQrS+gLqHtMRyN82fBMGcMUdqfUfzEstzVqCEDeaS5HuOBlK3bXzKkppjUTjksN3NQmcxgBz7RuJ9DqXCLXDcb2cwuafYCYOt+YLOEEgwDVm+t2P44dG5e46hO+fICH/7nP+WlpD5buz4GfMCgYB57r3g/6hi9WUDnfc7ZAzWMqR0EhJVYKYy+KFEtdIPzhkkIHq5RASe88E9kzoGoZFdb3tIjvGZWcHerirrqWkMsuQtP/Qi0zjieid5tAPj+r4kbiCVTw0E0jnmPBzGInQi7lpeTTKnG1fbyS5lBS+WmHfIuzpECgCkxhaT+LJJkg==\n-----END PRIVATE KEY-----"; + + private $deviceId = ''; + private $token = ''; + + protected function getHeaders() { + if (empty($this->deviceId)) { + $this->deviceId = $this->generateDid(); + } + if (empty($this->token)) { + $this->token = $this->getToken(); + } + + return [ + 'User-Agent' => 'okhttp/4.12.0', + 'client' => 'app', + 'deviceType' => 'Android', + 'deviceId' => $this->deviceId, + 'token' => $this->token, + 'Content-Type' => 'application/json' + ]; + } + + private function generateDid() { + $hex = '0123456789abcdef'; + $did = ''; + for ($i = 0; $i < 16; $i++) { + $did .= $hex[mt_rand(0, 15)]; + } + return $did; + } + + private function getToken() { + $url = $this->host . '/api/v1/app/user/visitorInfo'; + $headers = [ + 'User-Agent' => 'okhttp/4.12.0', + 'client' => 'app', + 'deviceType' => 'Android', + 'deviceId' => $this->deviceId + ]; + + $jsonStr = $this->fetch($url, [], $headers); + $json = json_decode($jsonStr, true); + + if (isset($json['code']) && $json['code'] === 200 && isset($json['data']['token'])) { + return $json['data']['token']; + } + return ''; + } + + private function rsaEncrypt($data) { + if (openssl_public_encrypt($data, $encrypted, $this->publicKey, OPENSSL_PKCS1_PADDING)) { + return base64_encode($encrypted); + } + return ''; + } + + private function rsaDecrypt($data) { + $decoded = base64_decode($data); + + $keyRes = openssl_pkey_get_private($this->privateKey); + $details = openssl_pkey_get_details($keyRes); + $keySize = ceil($details['bits'] / 8); // 128 for 1024 bit + + $result = ''; + $chunks = str_split($decoded, $keySize); + + foreach ($chunks as $chunk) { + if (openssl_private_decrypt($chunk, $decrypted, $this->privateKey, OPENSSL_PKCS1_PADDING)) { + $result .= $decrypted; + } else { + // error_log("Decrypt failed for chunk"); + } + } + + return $result; + } + + public function homeContent($filter) { + $url = $this->host . '/api/v1/app/screen/screenType'; + $jsonStr = $this->fetch($url, [ + CURLOPT_POST => 1, + CURLOPT_HTTPHEADER => $this->formatHeaders($this->getHeaders()) + ]); + + $json = json_decode($jsonStr, true); + $classes = []; + $filterObj = []; + + if (isset($json['data'])) { + foreach ($json['data'] as $mainCate) { + $typeId = (string)$mainCate['id']; + $classes[] = [ + 'type_id' => $typeId, + 'type_name' => $mainCate['name'] + ]; + + $filters = []; + if (isset($mainCate['children'])) { + foreach ($mainCate['children'] as $subCate) { + $filterType = ''; + switch ($subCate['name']) { + case '类型': $filterType = 'type'; break; + case '地区': $filterType = 'area'; break; + case '年份': $filterType = 'year'; break; + } + + if ($filterType) { + $values = [['n' => '全部', 'v' => '']]; + foreach ($subCate['children'] as $item) { + $values[] = ['n' => $item['name'], 'v' => $item['name']]; + } + $filters[] = [ + 'key' => $filterType, + 'name' => $subCate['name'], + 'value' => $values + ]; + } + } + } + + $filters[] = [ + 'key' => 'sort', + 'name' => '排序', + 'value' => [ + ['n' => '最新', 'v' => 'NEWEST'], + ['n' => '人气', 'v' => 'POPULARITY'], + ['n' => '评分', 'v' => 'COLLECT'], + ['n' => '热搜', 'v' => 'HOT'] + ] + ]; + + $filterObj[$typeId] = $filters; + } + } + + return [ + 'class' => $classes, + 'filters' => $filterObj + ]; + } + + public function categoryContent($tid, $pg = 1, $filter = [], $extend = []) { + $url = $this->host . '/api/v1/app/screen/screenMovie'; + + $condition = [ + 'classify' => $extend['type'] ?? '', + 'region' => $extend['area'] ?? '', + 'sreecnTypeEnum' => $extend['sort'] ?? 'NEWEST', + 'typeId' => $tid, + 'year' => $extend['year'] ?? '' + ]; + + $params = [ + 'condition' => $condition, + 'pageNum' => (int)$pg, + 'pageSize' => 40 + ]; + + $jsonStr = $this->fetch($url, [ + CURLOPT_POST => 1, + CURLOPT_POSTFIELDS => json_encode($params), + CURLOPT_HTTPHEADER => $this->formatHeaders($this->getHeaders()) + ]); + + $json = json_decode($jsonStr, true); + $videos = []; + + if (isset($json['data']['records'])) { + foreach ($json['data']['records'] as $item) { + $videos[] = [ + 'vod_id' => $item['id'] . '*' . $item['typeId'], + 'vod_name' => $item['name'], + 'vod_pic' => $item['cover'], + 'vod_remarks' => $item['totalEpisode'] ?? '' + ]; + } + } + + $total = $json['data']['total'] ?? 0; + return $this->pageResult($videos, $pg, $total, 40); + } + + public function detailContent($ids) { + $id = is_array($ids) ? $ids[0] : $ids; + $parts = explode('*', $id); + $vodId = (int)$parts[0]; + $typeId = $parts[1] ?? ''; + + // 1. 获取基本详情 + $detailUrl = $this->host . '/api/v1/app/play/movieDesc'; + $detailParams = ['id' => $vodId, 'typeId' => $typeId]; + + $detailRes = $this->fetch($detailUrl, [ + CURLOPT_POST => 1, + CURLOPT_POSTFIELDS => json_encode($detailParams), + CURLOPT_HTTPHEADER => $this->formatHeaders($this->getHeaders()) + ]); + + $detailJson = json_decode($detailRes, true); + $detailData = $detailJson['data'] ?? []; + + // 2. 获取播放列表 (加密) + $playReqPayload = json_encode([ + 'id' => $vodId, + 'source' => 0, + 'typeId' => $typeId + ]); + + $playParams = ['key' => $this->rsaEncrypt($playReqPayload)]; + + $playDataRes = $this->fetch($this->host . '/api/v1/app/play/movieDetails', [ + CURLOPT_POST => 1, + CURLOPT_POSTFIELDS => json_encode($playParams), + CURLOPT_HTTPHEADER => $this->formatHeaders($this->getHeaders()) + ]); + + $playJson = json_decode($playDataRes, true); + $playDataEnc = $playJson['data'] ?? ''; + + $decryptedDataStr = $this->rsaDecrypt($playDataEnc); + + $decryptedData = json_decode($decryptedDataStr, true); + + $shows = []; + $playUrls = []; + + if (isset($decryptedData['moviePlayerList'])) { + foreach ($decryptedData['moviePlayerList'] as $player) { + // 3. 获取具体集数 (加密) + $episodePayload = json_encode([ + 'id' => $vodId, + 'source' => 0, + 'typeId' => $typeId, + 'playerId' => $player['id'] + ]); + + $episodeParams = ['key' => $this->rsaEncrypt($episodePayload)]; + + $episodeRes = $this->fetch($this->host . '/api/v1/app/play/movieDetails', [ + CURLOPT_POST => 1, + CURLOPT_POSTFIELDS => json_encode($episodeParams), + CURLOPT_HTTPHEADER => $this->formatHeaders($this->getHeaders()) + ]); + + $episodeJson = json_decode($episodeRes, true); + $episodeDataEnc = $episodeJson['data'] ?? ''; + + $episodeDecStr = $this->rsaDecrypt($episodeDataEnc); + $episodeDecData = json_decode($episodeDecStr, true); + + $urls = []; + if (isset($episodeDecData['episodeList'])) { + foreach ($episodeDecData['episodeList'] as $ep) { + $param = [ + 'id' => $vodId, + 'typeId' => $typeId, + 'playerId' => $player['id'], + 'episodeId' => $ep['id'] + ]; + // 封装参数到URL中 + $urls[] = $ep['episode'] . '$' . json_encode($param); + } + } + + if (!empty($urls)) { + $shows[] = $player['moviePlayerName']; + $playUrls[] = implode('#', $urls); + } + } + } + + $vod = [ + 'vod_id' => $id, + 'vod_name' => $detailData['name'] ?? '', + 'vod_pic' => $detailData['cover'] ?? '', + 'vod_year' => $detailData['year'] ?? '', + 'vod_area' => $detailData['area'] ?? '', + 'vod_remarks' => $detailData['totalEpisode'] ?? '', + 'vod_actor' => $detailData['star'] ?? '', + 'vod_content' => $detailData['introduce'] ?? '', + 'vod_play_from' => implode('$$$', $shows), + 'vod_play_url' => implode('$$$', $playUrls) + ]; + + return ['list' => [$vod]]; + } + + public function searchContent($key, $quick = false, $pg = 1) { + $url = $this->host . '/api/v1/app/search/searchMovie'; + $params = [ + 'condition' => ['value' => $key], + 'pageNum' => (int)$pg, + 'pageSize' => 40 + ]; + + $jsonStr = $this->fetch($url, [ + CURLOPT_POST => 1, + CURLOPT_POSTFIELDS => json_encode($params), + CURLOPT_HTTPHEADER => $this->formatHeaders($this->getHeaders()) + ]); + + $json = json_decode($jsonStr, true); + $videos = []; + + if (isset($json['data']['records'])) { + foreach ($json['data']['records'] as $item) { + $videos[] = [ + 'vod_id' => $item['id'] . '*' . $item['typeId'], + 'vod_name' => $item['name'], + 'vod_pic' => $item['cover'], + 'vod_remarks' => $item['totalEpisode'] ?? '' + ]; + } + } + + $total = $json['data']['total'] ?? 0; + return $this->pageResult($videos, $pg, $total, 40); + } + + public function playerContent($flag, $id, $vipFlags = []) { + // $id 是 detailContent 中封装的 JSON 参数 + $param = json_decode($id, true); + + $urlPayload = json_encode([ + 'id' => $param['id'], + 'source' => 0, + 'typeId' => $param['typeId'], + 'playerId' => $param['playerId'], + 'episodeId' => $param['episodeId'] + ]); + + $urlParams = ['key' => $this->rsaEncrypt($urlPayload)]; + + $postData = $this->fetch($this->host . '/api/v1/app/play/movieDetails', [ + CURLOPT_POST => 1, + CURLOPT_POSTFIELDS => json_encode($urlParams), + CURLOPT_HTTPHEADER => $this->formatHeaders($this->getHeaders()) + ]); + + $json = json_decode($postData, true); + $encryptedUrl = $json['data'] ?? ''; + + $decryptedUrlDataStr = $this->rsaDecrypt($encryptedUrl); + $playerUrlData = json_decode($decryptedUrlDataStr, true); + $playerUrl = $playerUrlData['url'] ?? ''; + + // 最后一步分析 URL + $analysisUrl = $this->host . '/api/v1/app/play/analysisMovieUrl?playerUrl=' . urlencode($playerUrl) . '&playerId=' . $param['playerId']; + + $analysisRes = $this->fetch($analysisUrl, [ + CURLOPT_HTTPHEADER => $this->formatHeaders($this->getHeaders()) + ]); + + $analysisJson = json_decode($analysisRes, true); + $finalUrl = $analysisJson['data'] ?? ''; + + return [ + 'parse' => 0, + 'url' => $finalUrl, + 'header' => [ + 'User-Agent' => 'okhttp/4.12.0' + ] + ]; + } + + // 辅助方法:将关联数组 headers 转换为 curl 需要的格式 + private function formatHeaders($headers) { + $formatted = []; + foreach ($headers as $k => $v) { + $formatted[] = "$k: $v"; + } + return $formatted; + } +} + +// 运行爬虫 +(new Spider())->run(); diff --git "a/spider/php/\351\272\273\351\233\200\350\247\206\351\242\221 \341\265\210\341\266\273.php" "b/spider/php/\351\272\273\351\233\200\350\247\206\351\242\221 \341\265\210\341\266\273.php" new file mode 100644 index 00000000..0d9e9db5 --- /dev/null +++ "b/spider/php/\351\272\273\351\233\200\350\247\206\351\242\221 \341\265\210\341\266\273.php" @@ -0,0 +1,190 @@ +<?php +require_once __DIR__ . '/lib/spider.php'; + +class Spider extends BaseSpider { + private $HOST = 'https://www.mqtv.cc'; + private $KEY = 'Mcxos@mucho!nmme'; + private $UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36'; + + /** + * 对应 JS 中的 encodeData 和 decodeData (XOR + Base64) + */ + private function mq_xor_codec($data, $key, $is_decode = false) { + if ($is_decode) { + $data = base64_decode($data); + } else { + $data = json_encode($data, JSON_UNESCAPED_UNICODE); + $data = base64_encode($data); + } + + $res = ''; + $keyLen = strlen($key); + for ($i = 0; $i < strlen($data); $i++) { + $res .= $data[$i] ^ $key[$i % $keyLen]; + } + + if ($is_decode) { + return json_decode(base64_decode($res), true); + } else { + return urlencode(base64_encode($res)); + } + } + + private function getHeaders($referer = '/') { + return [ + 'User-Agent: ' . $this->UA, + 'Referer: ' . $this->HOST . $referer, + 'X-Requested-With: XMLHttpRequest' + ]; + } + + // 获取页面 PageID 并生成 Token + private function getToken($path, $ref = '/') { + $html = $this->fetch($this->HOST . $path, [], $this->getHeaders($ref)); + preg_match("/window\.pageid\s?=\s?'(.*?)';/i", $html, $m); + $pageId = $m[1] ?? ""; + return $this->mq_xor_codec($pageId, $this->KEY); + } + + public function homeContent($filter) { + // 5. 首页 (homeVod) + $token = $this->getToken('/'); + $apiUrl = $this->HOST . "/libs/VodList.api.php?home=index&token=$token"; + $resp = json_decode($this->fetch($apiUrl, [], $this->getHeaders()), true); + $list = []; + if (isset($resp['data']['movie'])) { + foreach ($resp['data']['movie'] as $section) { + foreach ($section['show'] as $v) { + $list[] = [ + 'vod_id' => $v['url'], + 'vod_name' => $v['title'], + 'vod_pic' => $v['img'], + 'vod_remarks' => $v['remark'] + ]; + } + } + } + + return [ + 'class' => [ + ['type_id' => '/type/movie', 'type_name' => '电影'], + ['type_id' => '/type/tv', 'type_name' => '电视剧'], + ['type_id' => '/type/va', 'type_name' => '综艺'], + ['type_id' => '/type/ct', 'type_name' => '动漫'] + ], + 'list' => array_slice($list, 0, 30) + ]; + } + + public function categoryContent($tid, $pg = 1, $filter = [], $extend = []) { + $typeKey = explode('/', trim($tid, '/'))[1] ?? 'movie'; + $token = $this->getToken($tid); + $apiUrl = $this->HOST . "/libs/VodList.api.php?type=$typeKey&rank=rankhot&page=$pg&token=$token"; + + $resp = json_decode($this->fetch($apiUrl, [], $this->getHeaders($tid)), true); + $list = []; + if (isset($resp['data'])) { + foreach ($resp['data'] as $v) { + $list[] = [ + 'vod_id' => $v['url'], + 'vod_name' => $v['title'], + 'vod_pic' => $v['img'], + 'vod_remarks' => $v['remark'] + ]; + } + } + return $this->pageResult($list, $pg); + } + + public function detailContent($ids) { + $id = is_array($ids) ? $ids[0] : $ids; + $pathParts = explode('/', trim($id, '/')); + $realId = end($pathParts); + $token = $this->getToken($id); + + $apiUrl = $this->HOST . "/libs/VodInfo.api.php?type=ct&id=$realId&token=$token"; + $json = json_decode($this->fetch($apiUrl, [], $this->getHeaders($id)), true); + $data = $json['data']; + + // 处理解析线路 + $parsesArr = []; + foreach (($data['playapi'] ?? []) as $p) { + if (isset($p['url'])) { + $parsesArr[] = (strpos($p['url'], '//') === 0) ? "https:" . $p['url'] : $p['url']; + } + } + $parsesStr = implode(',', $parsesArr); + + $playFrom = []; + $playUrls = []; + foreach (($data['playinfo'] ?? []) as $site) { + $playFrom[] = $site['cnsite']; + $urls = []; + foreach ($site['player'] as $ep) { + // 将解析接口封装在 URL 后面,供 play 阶段调用 + $urls[] = $ep['no'] . '$' . $ep['url'] . '@' . $parsesStr; + } + $playUrls[] = implode('#', $urls); + } + + $vod = [ + 'vod_id' => $id, + 'vod_name' => $data['title'], + 'vod_pic' => $data['img'], + 'vod_remarks' => $data['remark'], + 'vod_year' => $data['year'], + 'vod_area' => $data['area'], + 'vod_actor' => $data['actor'], + 'vod_director' => $data['director'], + 'vod_content' => $data['content'] ?? '', + 'vod_play_from' => implode('$$$', $playFrom), + 'vod_play_url' => implode('$$$', $playUrls) + ]; + + return ['list' => [$vod]]; + } + + public function searchContent($key, $quick = false, $pg = 1) { + $path = '/search/' . urlencode($key); + $token = $this->getToken($path); + $apiUrl = $this->HOST . "/libs/VodList.api.php?search=" . urlencode($key) . "&token=$token"; + + $resp = json_decode($this->fetch($apiUrl, [], $this->getHeaders($path)), true); + $data = $this->mq_xor_codec($resp['data'], $this->KEY, true); // 搜索数据需要解密 + + $list = []; + if (isset($data['vod_all'])) { + foreach ($data['vod_all'] as $item) { + foreach ($item['show'] as $v) { + $list[] = [ + 'vod_id' => $v['url'], + 'vod_name' => $v['title'], + 'vod_pic' => $v['img'], + 'vod_remarks' => $v['remark'] + ]; + } + } + } + return $this->pageResult($list, $pg); + } + + public function playerContent($flag, $id, $vipFlags = []) { + $parts = explode('@', $id); + $rawUrl = $parts[0]; + $parses = isset($parts[1]) ? explode(',', $parts[1]) : []; + + // 默认返回第一个解析地址配合嗅探,模拟 JS 中的逻辑 + $finalUrl = $rawUrl; + if (!empty($parses)) { + $finalUrl = $parses[0] . $rawUrl; + } + + return [ + 'parse' => 1, + 'url' => $finalUrl, + 'header' => ['User-Agent' => $this->UA] + ]; + } +} + +(new Spider())->run(); diff --git a/utils/api_helper.js b/utils/api_helper.js index ef142b88..936b85d8 100644 --- a/utils/api_helper.js +++ b/utils/api_helper.js @@ -33,7 +33,7 @@ export function startJsonWatcher(ENGINES, jsonDir) { // 设置新的防抖计时器,避免频繁触发 const timer = setTimeout(() => { - console.log(`${filename}文件已${eventType},即将清除所有模块缓存`); + console.log(`[HotReload] ${filename} changed, clearing cache...`); // 清除drpyS引擎的所有缓存 ENGINES.drpyS.clearAllCache(); // 清理已完成的计时器 @@ -44,7 +44,7 @@ export function startJsonWatcher(ENGINES, jsonDir) { } }); - console.log(`start json file hot reload success,listening path: ${jsonDir}`); + // console.log(`start json file hot reload success,listening path: ${jsonDir}`); } catch (error) { console.error('start json file listening failed with error:', error); } @@ -74,6 +74,12 @@ export function getApiEngine(engines, moduleName, query, options) { moduleDir = options.pyDir; _ext = '.py'; break; + case 'php': + // PHP引擎 - php + apiEngine = engines.php; + moduleDir = options.phpDir; + _ext = '.php'; + break; case 'cat': // CatVod引擎 apiEngine = engines.catvod; diff --git a/utils/binHelper.js b/utils/binHelper.js new file mode 100644 index 00000000..ceecb7e6 --- /dev/null +++ b/utils/binHelper.js @@ -0,0 +1,49 @@ +import fs from 'fs'; +import path from 'path'; + +/** + * 确保文件具有执行权限 (Linux/macOS) + * @param {string} filePath 文件绝对路径 + */ +export function ensureExecutable(filePath) { + if (process.platform === "win32") { + // Windows 不需要 chmod,直接返回 + return; + } + try { + if (!fs.existsSync(filePath)) { + return; + } + const stats = fs.statSync(filePath); + if (!(stats.mode & 0o111)) { + fs.chmodSync(filePath, 0o755); + console.log(`[binHelper] 已为文件 ${filePath} 添加执行权限`); + } + } catch (err) { + console.error(`[binHelper] 无法设置执行权限: ${filePath}`, err.message); + } +} + +/** + * 检查并准备二进制文件(检查存在性 + 赋予权限) + * @param {string} binPath 二进制文件路径或命令 + * @returns {string|null} 如果是现有文件路径返回路径,如果是全局命令返回原命令,如果文件不存在返回 null + */ +export function prepareBinary(binPath) { + if (!binPath) return null; + + // 如果不包含路径分隔符,假定是全局命令(如 'php', 'node'),直接返回 + // 注意:这里简单判断,如果用户写 ./php 或 /usr/bin/php 都会进入下面的 exist 检查 + if (!binPath.includes('/') && !binPath.includes('\\')) { + return binPath; + } + + // 如果是路径,检查是否存在 + if (fs.existsSync(binPath)) { + ensureExecutable(binPath); + return binPath; + } + + // 路径不存在 + return null; +} diff --git a/utils/createAxiosAgent.js b/utils/createAxiosAgent.js index 4c8f5599..ceb8bfdd 100644 --- a/utils/createAxiosAgent.js +++ b/utils/createAxiosAgent.js @@ -39,14 +39,27 @@ export function createAxiosInstance(options = {}) { const httpsAgent = new https.Agent(httpsAgentOptions); - // 配置 axios 使用代理 const _axios = axios.create({ - httpAgent, // 用于 HTTP 请求的代理 - httpsAgent, // 用于 HTTPS 请求的代理 + httpAgent, + httpsAgent, + }); + + _axios.interceptors.request.use(config => { + if (config && config.headers) { + const headers = config.headers; + const keys = Object.keys(headers); + for (const key of keys) { + if (key.toLowerCase() === 'user-agent' && headers[key] === 'RemoveUserAgent') { + delete headers[key]; + break; + } + } + } + return config; }); return _axios; } // 默认导出 -export default createAxiosInstance; \ No newline at end of file +export default createAxiosInstance; diff --git a/utils/esm-register.mjs b/utils/esm-register.mjs index 8dc51f51..d0a46162 100644 --- a/utils/esm-register.mjs +++ b/utils/esm-register.mjs @@ -12,7 +12,7 @@ export async function load(url, context, nextLoad) { const relativeUrl = url.replaceAll('assets://js/lib/', '../catLib/'); const catLibJsPath = path.join(assets_path, relativeUrl); const catLibHref = pathToFileURL(catLibJsPath).href; - console.log(`[assets url]: ${url} [relativeUrl]:${relativeUrl}\n[catLibJsPath]: ${catLibJsPath} [catLibHref]:${catLibHref}`); + // console.log(`[assets url]: ${url} [relativeUrl]:${relativeUrl}\n[catLibJsPath]: ${catLibJsPath} [catLibHref]:${catLibHref}`); url = catLibHref; } // 解决不了CAT_DEBUG=0模式下的相对路径依赖问题 diff --git a/utils/file.js b/utils/file.js index 07da6728..420e67a5 100644 --- a/utils/file.js +++ b/utils/file.js @@ -148,10 +148,22 @@ export function getParsesDict(host) { const jx_conf_text = readFileSync(jx_conf, 'utf-8'); let jx_conf_content = jx_conf_text.trim(); - // 准备模板变量字典 + let hostName = host; + try { + const raw = String(host || ''); + const hasScheme = raw.includes('://'); + const u = new URL(hasScheme ? raw : `http://${raw}`); + const hostname = u.hostname || raw; + const safeHostname = hostname.includes(':') ? `[${hostname}]` : hostname; + hostName = hasScheme ? `${u.protocol}//${safeHostname}` : safeHostname; + } catch (e) { + const raw = String(host || '').replace(/^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//, '').split('/')[0]; + hostName = raw.startsWith('[') ? raw.split(']')[0] + ']' : raw.split(':')[0]; + } + let var_dict = { host, - hostName: host.split(':').length > 1 ? host.slice(0, host.lastIndexOf(":")) : host + hostName }; // 使用Jinja模板引擎渲染配置内容 @@ -213,4 +225,4 @@ export function executeParse(name, host, url) { } globalThis.pathLib = pathLib; -globalThis.executeParse = executeParse; \ No newline at end of file +globalThis.executeParse = executeParse; diff --git a/utils/phpEnv.js b/utils/phpEnv.js new file mode 100644 index 00000000..31f3d54e --- /dev/null +++ b/utils/phpEnv.js @@ -0,0 +1,33 @@ +import { execFile } from 'child_process'; +import { promisify } from 'util'; +import { prepareBinary } from './binHelper.js'; + +const execFileAsync = promisify(execFile); + +export let isPhpAvailable = false; + +export const checkPhpAvailable = async () => { + let phpPath = process.env.PHP_PATH || 'php'; + + // Check existence and permissions + const validPath = prepareBinary(phpPath); + if (!validPath) { + console.warn(`⚠️ PHP binary not found or invalid: ${phpPath}`); + isPhpAvailable = false; + return false; + } + phpPath = validPath; + + try { + console.log(`[phpEnv] Verifying PHP executable: ${phpPath}`); + await execFileAsync(phpPath, ['-v']); + isPhpAvailable = true; + console.log(`✅ PHP environment check passed (${phpPath}).`); + } catch (e) { + isPhpAvailable = false; + console.warn(`⚠️ PHP environment check failed. PHP features will be disabled.`); + console.warn(`[phpEnv] Error details:`, e.message); + // console.error(e); + } + return isPhpAvailable; +}; diff --git a/utils/pluginManager.js b/utils/pluginManager.js index a7bf059f..5a58d30c 100644 --- a/utils/pluginManager.js +++ b/utils/pluginManager.js @@ -2,6 +2,7 @@ import fs from "fs"; import path from "path"; import {spawn} from "child_process"; import {fileURLToPath, pathToFileURL} from "url"; +import {ensureExecutable} from "./binHelper.js"; // 获取 pluginManager.js 的目录 const __filename = fileURLToPath(import.meta.url); @@ -14,7 +15,7 @@ const exampleConfigPath = path.join(__dirname, "../.plugins.example.js"); // 尝试加载用户配置,如果没有就用 example let plugins = []; try { - console.log(`检查插件配置文件: ${userConfigPath} 是否存在`); + // console.log(`检查插件配置文件: ${userConfigPath} 是否存在`); if (fs.existsSync(userConfigPath)) { plugins = (await import(pathToFileURL(userConfigPath).href)).default; console.log("[pluginManager] 使用用户 .plugins.js 配置"); @@ -54,22 +55,6 @@ function getPluginBinary(rootDir, pluginPath, pluginName) { return path.join(binDir, binaryName); } -function ensureExecutable(filePath) { - if (process.platform === "win32") { - // Windows 不需要 chmod,直接返回 - return; - } - try { - const stats = fs.statSync(filePath); - if (!(stats.mode & 0o111)) { - fs.chmodSync(filePath, 0o755); - console.log(`[pluginManager] 已为插件 ${filePath} 添加执行权限`); - } - } catch (err) { - console.error(`[pluginManager] 无法设置执行权限: ${filePath}`, err.message); - } -} - /** * 启动插件 * @param {Object} plugin 插件配置 diff --git a/utils/proxy-util.js b/utils/proxy-util.js index d4f689ac..01cd3425 100644 --- a/utils/proxy-util.js +++ b/utils/proxy-util.js @@ -70,7 +70,7 @@ export class SmartCacheManager { // 启动定期清理任务 this.startCleanupTimer(); - console.log(`[${this.name}] SmartCacheManager initialized: maxSize=${this.maxSize}, defaultTTL=${this.defaultTTL}ms`); + // console.log(`[${this.name}] SmartCacheManager initialized: maxSize=${this.maxSize}, defaultTTL=${this.defaultTTL}ms`); } /**