123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533 |
- import {readdirSync, readFileSync, writeFileSync, existsSync} from 'fs';
- import path from 'path';
- import * as drpy from '../libs/drpyS.js';
- import '../libs_drpy/jinja.js'
- import {naturalSort, urljoin, updateQueryString} from '../utils/utils.js'
- import {md5} from "../libs_drpy/crypto-util.js";
- import {ENV} from "../utils/env.js";
- 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';
- const {jsEncoder} = drpy;
- // 工具函数:生成 JSON 数据
- async function generateSiteJSON(options, requestHost, sub, pwd) {
- const jsDir = options.jsDir;
- const dr2Dir = options.dr2Dir;
- const configDir = options.configDir;
- const subFilePath = options.subFilePath;
- const rootDir = options.rootDir;
- const files = readdirSync(jsDir);
- let valid_files = files.filter((file) => file.endsWith('.js') && !file.startsWith('_')); // 筛选出不是 "_" 开头的 .js 文件
- let sort_list = [];
- if (sub) {
- if (sub.mode === 0) {
- valid_files = valid_files.filter(it => (new RegExp(sub.reg || '.*')).test(it));
- } else if (sub.mode === 1) {
- valid_files = valid_files.filter(it => !(new RegExp(sub.reg || '.*')).test(it));
- }
- let sort_file = path.join(path.dirname(subFilePath), `./order_common.html`);
- if (!existsSync(sort_file)) {
- sort_file = path.join(path.dirname(subFilePath), `./order_common.example.html`);
- }
- if (sub.sort) {
- sort_file = path.join(path.dirname(subFilePath), `./${sub.sort}.html`);
- if (!existsSync(sort_file)) {
- sort_file = path.join(path.dirname(subFilePath), `./${sub.sort}.example.html`);
- }
- }
- if (existsSync(sort_file)) {
- console.log('sort_file:', sort_file);
- let sort_file_content = readFileSync(sort_file, 'utf-8');
- // console.log(sort_file_content)
- sort_list = sort_file_content.split('\n').filter(it => it.trim()).map(it => it.trim());
- // console.log(sort_list);
- }
- }
- let sites = [];
- let link_jar = '';
- // console.log('hide_adult:', ENV.get('hide_adult'));
- if (ENV.get('hide_adult') === '1') {
- valid_files = valid_files.filter(it => !(new RegExp('\\[[密]\\]|密+')).test(it));
- }
- let SitesMap = getSitesMap(configDir);
- // console.log(SitesMap);
- const tasks = valid_files.map((file) => {
- return {
- func: async ({file, jsDir, requestHost, pwd, drpy, SitesMap, jsEncoder}) => {
- const baseName = path.basename(file, '.js'); // 去掉文件扩展名
- let api = `${requestHost}/api/${baseName}`; // 使用请求的 host 地址,避免硬编码端口
- if (pwd) {
- api += `?pwd=${pwd}`;
- }
- let ruleObject = {
- searchable: 0, // 固定值
- filterable: 0, // 固定值
- quickSearch: 0, // 固定值
- };
- try {
- ruleObject = await drpy.getRuleObject(path.join(jsDir, file));
- } catch (e) {
- throw new Error(`Error parsing rule object for file: ${file}, ${e.message}`);
- }
- let fileSites = [];
- if (baseName === 'push_agent') {
- let key = 'push_agent';
- let name = `${ruleObject.title}(DS)`;
- fileSites.push({key, name});
- } else if (SitesMap.hasOwnProperty(baseName) && Array.isArray(SitesMap[baseName])) {
- SitesMap[baseName].forEach((it) => {
- let key = `drpyS_${it.alias}`;
- let name = `${it.alias}(DS)`;
- let ext = it.queryObject.type === 'url' ? it.queryObject.params : it.queryStr;
- if (ext) {
- ext = jsEncoder.gzip(ext);
- }
- fileSites.push({key, name, ext});
- });
- } else {
- let key = `drpyS_${baseName}`;
- let name = `${baseName}(DS)`;
- fileSites.push({key, name});
- }
- fileSites.forEach((fileSite) => {
- const site = {
- key: fileSite.key,
- name: fileSite.name,
- type: 4, // 固定值
- api,
- searchable: ruleObject.searchable,
- filterable: ruleObject.filterable,
- quickSearch: ruleObject.quickSearch,
- more: ruleObject.more,
- logo: ruleObject.logo,
- ext: fileSite.ext || "", // 固定为空字符串
- };
- sites.push(site);
- });
- },
- param: {file, jsDir, requestHost, pwd, drpy, SitesMap, jsEncoder},
- 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: {}, // 外部参数可以在这里传入
- };
- await batchExecute(tasks, listener);
- // 根据用户是否启用dr2源去生成对应配置
- if (ENV.get('enable_dr2', '1') === '1') {
- const dr2_files = readdirSync(dr2Dir);
- let dr2_valid_files = dr2_files.filter((file) => file.endsWith('.js') && !file.startsWith('_')); // 筛选出不是 "_" 开头的 .js 文件
- // log(dr2_valid_files);
- log(`开始生成dr2的t3配置,dr2Dir:${dr2Dir},源数量: ${dr2_valid_files.length}`);
- const dr2_tasks = dr2_valid_files.map((file) => {
- return {
- func: async ({file, dr2Dir, requestHost, pwd, drpy, SitesMap}) => {
- const baseName = path.basename(file, '.js'); // 去掉文件扩展名
- let api = `assets://js/lib/drpy2.js`; // 使用内置drpy2
- let ext = `${requestHost}/js/${file}`;
- if (pwd) {
- ext += `?pwd=${pwd}`;
- }
- let ruleObject = {
- searchable: 0, // 固定值
- filterable: 0, // 固定值
- quickSearch: 0, // 固定值
- };
- try {
- // console.log('file:', path.join(dr2Dir, file));
- ruleObject = await drpy.getRuleObject(path.join(dr2Dir, file));
- } catch (e) {
- throw new Error(`Error parsing rule object for file: ${file}, ${e.message}`);
- }
- let fileSites = [];
- if (baseName === 'push_agent') {
- let key = 'push_agent';
- let name = `${ruleObject.title}(DR2)`;
- fileSites.push({key, name, ext});
- } else if (SitesMap.hasOwnProperty(baseName) && Array.isArray(SitesMap[baseName])) {
- SitesMap[baseName].forEach((it) => {
- let key = `drpy2_${it.alias}`;
- let name = `${it.alias}(DR2)`;
- let _ext = updateQueryString(ext, it.queryStr);
- fileSites.push({key, name, ext: _ext});
- });
- } else {
- let key = `drpy2_${baseName}`;
- let name = `${baseName}(DR2)`;
- fileSites.push({key, name, ext});
- }
- fileSites.forEach((fileSite) => {
- const site = {
- key: fileSite.key,
- name: fileSite.name,
- type: 3, // 固定值
- api,
- searchable: ruleObject.searchable,
- filterable: ruleObject.filterable,
- quickSearch: ruleObject.quickSearch,
- more: ruleObject.more,
- logo: ruleObject.logo,
- ext: fileSite.ext || "", // 固定为空字符串
- };
- sites.push(site);
- });
- },
- param: {file, dr2Dir, requestHost, pwd, drpy, SitesMap},
- id: file,
- };
- });
- await batchExecute(dr2_tasks, listener);
- }
- // 根据用户是否启用挂载数据源去生成对应配置
- if (ENV.get('enable_link_data', '0') === '1') {
- log(`开始挂载外部T4数据`);
- let link_sites = [];
- let link_url = ENV.get('link_url');
- let enable_link_push = ENV.get('enable_link_push', '0');
- let enable_link_jar = ENV.get('enable_link_jar', '0');
- try {
- let link_data = readFileSync(path.join(rootDir, './data/settings/link_data.json'), 'utf-8');
- let link_config = JSON.parse(link_data);
- link_sites = link_config.sites.filter(site => site.type = 4);
- if (link_config.spider && enable_link_jar === '1') {
- let link_spider_arr = link_config.spider.split(';');
- link_jar = urljoin(link_url, link_spider_arr[0]);
- if (link_spider_arr.length > 1) {
- link_jar = [link_jar].concat(link_spider_arr.slice(1)).join(';')
- }
- log(`开始挂载外部T4 Jar: ${link_jar}`);
- }
- link_sites.forEach((site) => {
- if (site.key === 'push_agent' && enable_link_push !== '1') {
- return
- }
- if (site.api && !site.api.startsWith('http')) {
- site.api = urljoin(link_url, site.api)
- }
- if (site.ext && site.ext.startsWith('.')) {
- site.ext = urljoin(link_url, site.ext)
- }
- if (site.key === 'push_agent' && enable_link_push === '1') { // 推送覆盖
- let pushIndex = sites.findIndex(s => s.key === 'push_agent');
- if (pushIndex > -1) {
- sites[pushIndex] = site;
- } else {
- sites.push(site);
- }
- } else {
- sites.push(site);
- }
- });
- } catch (e) {
- }
- }
- // 订阅再次处理别名的情况
- if (sub) {
- if (sub.mode === 0) {
- sites = sites.filter(it => (new RegExp(sub.reg || '.*')).test(it.name));
- } else if (sub.mode === 1) {
- sites = sites.filter(it => !(new RegExp(sub.reg || '.*')).test(it.name));
- }
- }
- // 青少年模式再次处理自定义别名的情况
- if (ENV.get('hide_adult') === '1') {
- sites = sites.filter(it => !(new RegExp('\\[[密]\\]|密+')).test(it.name));
- }
- sites = naturalSort(sites, 'name', sort_list);
- return {sites, spider: link_jar};
- }
- 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 parses = [];
- const tasks = jx_files.map((file) => {
- return {
- func: async ({file, jxDir, requestHost, drpy}) => {
- 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 drpy.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
- });
- },
- param: {file, jxDir, requestHost, drpy},
- 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: {}, // 外部参数可以在这里传入
- };
- await batchExecute(tasks, listener);
- let 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};
- }
- function generateLivesJSON(requestHost) {
- let lives = [];
- let live_url = process.env.LIVE_URL || '';
- let epg_url = process.env.EPG_URL || ''; // 从.env文件读取
- let logo_url = process.env.LOGO_URL || ''; // 从.env文件读取
- if (live_url && !live_url.startsWith('http')) {
- let public_url = urljoin(requestHost, 'public/');
- live_url = urljoin(public_url, live_url);
- }
- // console.log('live_url:', live_url);
- if (live_url) {
- lives.push(
- {
- "name": "直播",
- "type": 0,
- "url": live_url,
- "playerType": 1,
- "ua": "okhttp/3.12.13",
- "epg": epg_url,
- "logo": logo_url
- }
- )
- }
- return {lives}
- }
- function generatePlayerJSON(configDir, requestHost) {
- let playerConfig = {};
- let playerConfigPath = path.join(configDir, './player.json');
- if (existsSync(playerConfigPath)) {
- try {
- playerConfig = JSON.parse(readFileSync(playerConfigPath, 'utf-8'))
- } catch (e) {
- }
- }
- return playerConfig
- }
- function getSubs(subFilePath) {
- let subs = [];
- try {
- const subContent = readFileSync(subFilePath, 'utf-8');
- subs = JSON.parse(subContent)
- } catch (e) {
- console.log(`读取订阅失败:${e.message}`);
- }
- return subs
- }
- export default (fastify, options, done) => {
- fastify.get('/index', {preHandler: validatePwd}, async (request, reply) => {
- if (!existsSync(options.indexFilePath)) {
- reply.code(404).send({error: 'index.json not found'});
- return;
- }
- const content = readFileSync(options.indexFilePath, 'utf-8');
- reply.send(JSON.parse(content));
- });
- // 接口:返回配置 JSON,同时写入 index.json
- fastify.get('/config*', {preHandler: [validatePwd, validateBasicAuth]}, async (request, reply) => {
- let t1 = (new Date()).getTime();
- const query = request.query; // 获取 query 参数
- const pwd = query.pwd || '';
- const sub_code = query.sub || '';
- const cfg_path = request.params['*']; // 捕获整个路径
- try {
- // 获取主机名,协议及端口
- const protocol = request.headers['x-forwarded-proto'] || (request.socket.encrypted ? 'https' : 'http'); // http 或 https
- const hostname = request.hostname; // 主机名,不包含端口
- const port = request.socket.localPort; // 获取当前服务的端口
- console.log(`cfg_path:${cfg_path},port:${port}`);
- let not_local = cfg_path.startsWith('/1') || cfg_path.startsWith('/index');
- let requestHost = not_local ? `${protocol}://${hostname}` : `http://127.0.0.1:${options.PORT}`; // 动态生成根地址
- let requestUrl = not_local ? `${protocol}://${hostname}${request.url}` : `http://127.0.0.1:${options.PORT}${request.url}`; // 动态生成请求链接
- // console.log('requestUrl:', requestUrl);
- // if (cfg_path.endsWith('.js')) {
- // if (cfg_path.includes('index.js')) {
- // // return reply.sendFile('index.js', path.join(options.rootDir, 'data/cat'));
- // let content = readFileSync(path.join(options.rootDir, 'data/cat/index.js'), 'utf-8');
- // // content = jinja.render(content, {config_url: requestUrl.replace(cfg_path, `/1?sub=all&pwd=${process.env.API_PWD || ''}`)});
- // content = content.replace('$config_url', requestUrl.replace(cfg_path, `/1?sub=all&pwd=${process.env.API_PWD || ''}`));
- // return reply.type('application/javascript;charset=utf-8').send(content);
- // } else if (cfg_path.includes('index.config.js')) {
- // let content = readFileSync(path.join(options.rootDir, 'data/cat/index.config.js'), 'utf-8');
- // // content = jinja.render(content, {config_url: requestUrl.replace(cfg_path, `/1?sub=all&pwd=${process.env.API_PWD || ''}`)});
- // content = content.replace('$config_url', requestUrl.replace(cfg_path, `/1?sub=all&pwd=${process.env.API_PWD || ''}`));
- // return reply.type('application/javascript;charset=utf-8').send(content);
- // }
- // }
- // if (cfg_path.endsWith('.js.md5')) {
- // if (cfg_path.includes('index.js')) {
- // let content = readFileSync(path.join(options.rootDir, 'data/cat/index.js'), 'utf-8');
- // // content = jinja.render(content, {config_url: requestUrl.replace(cfg_path, `/1?sub=all&pwd=${process.env.API_PWD || ''}`)});
- // content = content.replace('$config_url', requestUrl.replace(cfg_path, `/1?sub=all&pwd=${process.env.API_PWD || ''}`));
- // let contentHash = md5(content);
- // console.log('index.js contentHash:', contentHash);
- // return reply.type('text/plain;charset=utf-8').send(contentHash);
- // } else if (cfg_path.includes('index.config.js')) {
- // let content = readFileSync(path.join(options.rootDir, 'data/cat/index.config.js'), 'utf-8');
- // // content = jinja.render(content, {config_url: requestUrl.replace(cfg_path, `/1?sub=all&pwd=${process.env.API_PWD || ''}`)});
- // content = content.replace('$config_url', requestUrl.replace(cfg_path, `/1?sub=all&pwd=${process.env.API_PWD || ''}`));
- // let contentHash = md5(content);
- // console.log('index.config.js contentHash:', contentHash);
- // return reply.type('text/plain;charset=utf-8').send(contentHash);
- // }
- // }
- const getFilePath = (cfgPath, rootDir, fileName) => path.join(rootDir, `data/cat/${fileName}`);
- const processContent = (content, cfgPath, requestUrl) =>
- content.replace('$config_url', requestUrl.replace(cfgPath, `/1?sub=all&pwd=${process.env.API_PWD || ''}`));
- const handleJavaScript = (cfgPath, requestUrl, options, reply) => {
- const fileMap = {
- 'index.js': 'index.js',
- 'index.config.js': 'index.config.js'
- };
- for (const [key, fileName] of Object.entries(fileMap)) {
- if (cfgPath.includes(key)) {
- const filePath = getFilePath(cfgPath, options.rootDir, fileName);
- let content = readFileSync(filePath, 'utf-8');
- content = processContent(content, cfgPath, requestUrl);
- return reply.type('application/javascript;charset=utf-8').send(content);
- }
- }
- };
- const handleJsMd5 = (cfgPath, requestUrl, options, reply) => {
- const fileMap = {
- 'index.js': 'index.js',
- 'index.config.js': 'index.config.js'
- };
- for (const [key, fileName] of Object.entries(fileMap)) {
- if (cfgPath.includes(key)) {
- const filePath = getFilePath(cfgPath, options.rootDir, fileName);
- let content = readFileSync(filePath, 'utf-8');
- content = processContent(content, cfgPath, requestUrl);
- const contentHash = md5(content);
- console.log(`${fileName} contentHash:`, contentHash);
- return reply.type('text/plain;charset=utf-8').send(contentHash);
- }
- }
- };
- if (cfg_path.endsWith('.js')) {
- return handleJavaScript(cfg_path, requestUrl, options, reply);
- }
- if (cfg_path.endsWith('.js.md5')) {
- return handleJsMd5(cfg_path, requestUrl, options, reply);
- }
- let sub = null;
- if (sub_code) {
- let subs = getSubs(options.subFilePath);
- sub = subs.find(it => it.code === sub_code);
- // console.log('sub:', sub);
- if (sub && sub.status === 0) {
- return reply.status(500).send({error: `此订阅码:【${sub_code}】已禁用`});
- }
- }
- const siteJSON = await generateSiteJSON(options, requestHost, sub, pwd);
- const parseJSON = await generateParseJSON(options.jxDir, requestHost);
- const livesJSON = generateLivesJSON(requestHost);
- const playerJSON = generatePlayerJSON(options.configDir, requestHost);
- const configObj = {sites_count: siteJSON.sites.length, ...playerJSON, ...siteJSON, ...parseJSON, ...livesJSON};
- if (!configObj.spider) {
- configObj.spider = playerJSON.spider
- }
- // console.log(configObj);
- const configStr = JSON.stringify(configObj, null, 2);
- if (!process.env.VERCEL) { // Vercel 环境不支持写文件,关闭此功能
- writeFileSync(options.indexFilePath, configStr, 'utf8'); // 写入 index.json
- if (cfg_path === '/1') {
- writeFileSync(options.customFilePath, configStr, 'utf8'); // 写入 index.json
- }
- }
- let t2 = (new Date()).getTime();
- let cost = t2 - t1;
- // configObj.cost = cost;
- // reply.send(configObj);
- reply.send(Object.assign({cost}, configObj));
- } catch (error) {
- reply.status(500).send({error: 'Failed to generate site JSON', details: error.message});
- }
- });
- done();
- };
|