import { GENERAL_REQ_LINK, GENERAL_REQ_LINK_3 } from '../constants/tools.js'; /** * 第三方接口适配器,用于大面积覆盖解析视频的内容 */ class GeneralLinkAdapter { constructor() {} /** * 暂时用这个来处理短链接 * @param url * @param includeRedirect * @returns {Promise} */ async fetchUrl(url, includeRedirect = false) { let response = await fetch(url, { headers: { 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36', }, }); return includeRedirect ? response.url : response; } /** * 【辅助函数】创造一个第三方接口的链接 * @param externalInterface 第三方接口:这个链接来自常量 constants/tools.js @GENERAL_REQ_LINK / ... * @param requestURL 请求的链接 * @returns {*} */ createReqLink(externalInterface, requestURL) { // 这里必须使用{ ...GENERAL_REQ_LINK_2 }赋值,不然就是对象的引用赋值,会造成全局数据问题! let reqLink = { ...externalInterface }; reqLink.link = reqLink.link.replace('{}', requestURL); return reqLink; } async ks(link) { // 例子:https://www.kuaishou.com/short-video/3xkfs8p4pnd67p4?authorId=3xkznsztpwetngu&streamSource=find&area=homexxbrilliant // https://v.m.chenzhongtech.com/fw/photo/3xburnkmj3auazc // https://v.kuaishou.com/1ff8QP let msg = /(?:https?:\/\/)?(www|v)\.(kuaishou|m\.chenzhongtech)\.com\/[A-Za-z\d._?%&+\-=\/#]*/g.exec( link )[0]; // 跳转短号 if (msg.includes('v.kuaishou')) { msg = await this.fetchUrl(msg, true); } let video_id; if (msg.includes('/fw/photo/')) { video_id = msg.match(/\/fw\/photo\/([^/?]+)/)[1]; } else if (msg.includes('short-video')) { video_id = msg.match(/short-video\/([^/?]+)/)[1]; } else { throw Error('无法提取快手的信息,请重试或者换一个视频!'); } const reqLink = this.createReqLink( GENERAL_REQ_LINK, `https://www.kuaishou.com/short-video/${video_id}` ); // 提取视频 return { name: '快手', reqLink, }; } async xigua(link) { // 1. https://v.ixigua.com/ienrQ5bR/ // 2. https://www.ixigua.com/7270448082586698281 // 3. https://m.ixigua.com/video/7270448082586698281 let msg = /(?:https?:\/\/)?(www|v|m)\.ixigua\.com\/[A-Za-z\d._?%&+\-=\/#]*/g.exec(link)[0]; // 跳转短号 if (msg.includes('v.ixigua')) { msg = await this.fetchUrl(msg, true); } const id = /ixigua\.com\/(\d+)/.exec(msg)[1] || /\/video\/(\d+)/.exec(msg)[1]; const videoReq = `https://www.ixigua.com/${id}`; const reqLink = this.createReqLink(GENERAL_REQ_LINK, videoReq); return { name: '西瓜', reqLink }; } async pipixia(link) { const msg = /https:\/\/h5\.pipix\.com\/(s|item)\/[A-Za-z0-9]+/.exec(link)?.[0]; const reqLink = this.createReqLink(GENERAL_REQ_LINK, msg); return { name: '皮皮虾', reqLink }; } async pipigx(link) { const msg = /https:\/\/h5\.pipigx\.com\/pp\/post\/[A-Za-z0-9]+/.exec(link)?.[0]; const reqLink = this.createReqLink(GENERAL_REQ_LINK, msg); return { name: '皮皮搞笑', reqLink }; } async tieba(link) { const msg = /https:\/\/tieba\.baidu\.com\/p\/[A-Za-z0-9]+/.exec(link)?.[0]; // 这里必须使用{ ...GENERAL_REQ_LINK_2 }赋值,不然就是对象的引用赋值,会造成全局数据问题! // !!!这里加了 '\?' 是因为 API 问题 const reqLink = this.createReqLink(GENERAL_REQ_LINK, msg + '"?'); return { name: '贴吧', reqLink }; } async qqSmallWorld(link) { const msg = /https:\/\/s.xsj\.qq\.com\/[A-Za-z0-9]+/.exec(link)?.[0]; const reqLink = this.createReqLink(GENERAL_REQ_LINK, msg); return { name: 'QQ小世界', reqLink }; } async jike(link) { // https://m.okjike.com/originalPosts/6583b4421f0812cca58402a6?s=ewoidSI6ICI1YTgzMTY4ZmRmNDA2MDAwMTE5N2MwZmQiCn0= const msg = /https:\/\/m.okjike.com\/originalPosts\/[A-Za-z0-9]+/.exec(link)?.[0]; const reqLink = this.createReqLink(GENERAL_REQ_LINK_3, msg); return { name: '即刻', reqLink }; } async douyinBackup(link) { const msg = /(http:\/\/|https:\/\/)v.douyin.com\/[A-Za-z\d._?%&+\-=\/#]*/.exec(link)?.[0]; const reqLink = this.createReqLink(GENERAL_REQ_LINK, msg); return { name: '抖音动图', reqLink }; } /** * 初始化通用适配器 * @param link 通用链接 * @returns {Promise<*>} */ async init(link) { logger.mark('[R插件][通用解析]', link); const handlers = new Map([ [/(kuaishou.com|chenzhongtech.com)/, this.ks.bind(this)], [/ixigua.com/, this.xigua.bind(this)], [/h5.pipix.com/, this.pipixia.bind(this)], [/h5.pipigx.com/, this.pipigx.bind(this)], [/tieba.baidu.com/, this.tieba.bind(this)], [/xsj.qq.com/, this.qqSmallWorld.bind(this)], [/m.okjike.com/, this.jike.bind(this)], [/v.douyin.com/, this.douyinBackup.bind(this)], ]); for (let [regex, handler] of handlers) { if (regex.test(link)) { return handler(link); } } } /** * 通用解析适配器,将其他的第三方接口转换为统一的接口 * @param adapter 通用解析适配器 * @param sign 通用解析标识:1、2 【在适配器的reqLink中】 * @returns {Promise} */ async resolve(adapter, sign) { // 发送GET请求 return fetch(adapter.reqLink.link, { headers: { Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7', 'Accept-Language': 'zh-CN,zh;q=0.9', 'Cache-Control': 'no-cache', Connection: 'keep-alive', Pragma: 'no-cache', 'Sec-Fetch-Dest': 'document', 'Sec-Fetch-Mode': 'navigate', 'Sec-Fetch-Site': 'none', 'Sec-Fetch-User': '?1', 'Upgrade-Insecure-Requests': '1', 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36', }, timeout: 10000, }).then(async (resp) => { const data = await resp.json(); if (sign === 1) { // @link GENERAL_REQ_LINK return { name: adapter.name, images: data.data?.imageUrl, video: data.data?.url, }; } else if (sign === 2) { // @link GENERAL_REQ_LINK_2 return { name: adapter.name, images: data.data?.images, video: data.data?.videoUrl, desc: data.data?.desc, }; } else if (sign === 3) { console.log(data); return { name: adapter.name, images: data?.images.map((item) => item.url), }; } else { throw Error('[R插件][通用解析]错误Sign标识'); } }); } /** * 通过工厂方式创建一个通用解析的JSON对象 * @param link * @returns {Promise<*>} */ static async create(link) { // 先正则匹配到函数进行出策略处理 const adapter = await new GeneralLinkAdapter(); const adapterHandler = await adapter.init(link); // 对处理完的信息进行通用解析 return adapter.resolve(adapterHandler, adapterHandler.reqLink.sign); } } export default GeneralLinkAdapter;