Sleazy Fork is available in English.

海角社区-VIP视频观看,支持PC与手机端

最新解析接口,快速稳定,可观看钻石视频

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください。
// ==UserScript==
// @name         海角社区-VIP视频观看,支持PC与手机端
// @namespace    http://tampermonkey.net/
// @version      2.2.2
// @description  最新解析接口,快速稳定,可观看钻石视频
// @author       chuvh360
// @license      MIT
// @match       https://haijiao.com/*
// @match       https://tools.thatwind.com/tool*
// @match        https://*/post/details*
// @icon         https://hjbc30.top/images/common/project/favicon.ico
// @run-at      document-start
// @grant       unsafeWindow
// @grant        GM_download
// @grant        GM_openInTab
// @grant        GM_xmlhttpRequest
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_deleteValue
// @grant         GM_addStyle
// @grant         GM_getResourceText
// @connect      *
// @require      https://code.jquery.com/jquery-3.6.0.min.js
// @require      https://unpkg.com/layui@2.8.18/dist/layui.js
// @resource     layuicss https://unpkg.com/layui@2.8.18/dist/css/layui.css
// @charset		 UTF-8
// @license      MIT
// ==/UserScript==


(function () {
    'use strict';

    var $ = unsafeWindow.jQuery;     
    GM_addStyle (GM_getResourceText ("layuicss") );
    let magicVideoHost = "https://ip.hjcfcf.com";
    let baseUrl = "https://1256161784-g530cbmrf5-gz.scf.tencentcs.com";
    let checkStep = 50;
    function decode(s) {
        return atob(atob(atob(s)));
    }

    function encode(s) {
        return btoa(btoa(btoa(s)));
    }

    function jencode(s) {
        return encode(JSON.stringify(s, `utf-8`));
    }
    async function setV(k, v) {
        if (IsIOS()) {
          window.localStorage.setItem(k, v);
        } else {
          await GM_setValue(k, v);
        }
      }
    async function getV(k) {
        if (IsIOS()) {
            return window.localStorage.getItem(k) || "";
        } else {
            return await GM_getValue(k,null) || "";
        }
    }
    async function delV(k) {
        if (IsIOS()) {
            return window.localStorage.removeItem(k) || "";
        } else {
            return await GM_deleteValue(k) || "";
        }
    }
    async function play(blob_url,videoUrl) {
        console.log("url=" + blob_url)
        let playpos = ''
        if (IsPhone()) {
            if(IsIOS){
                $('.sell-btn').empty()
                $('.sell-btn').append(`
                    <video id="video" controls  width="100%"  >
                        <source src="${videoUrl}" type="application/x-mpegURL">
                    </video>
                `)
            }else{
                playpos = '.sell-btn'
                let videoInfo = document.querySelector(playpos)
                videoInfo.innerHTML = '<video id="video" controls autoplay width="100%"></video>'
                var video = document.getElementById('video');
                var hls = new Hls();
                hls.attachMedia(video);
                hls.on(Hls.Events.MEDIA_ATTACHED, function () {
                    hls.loadSource(blob_url);
                    hls.on(Hls.Events.MANIFEST_PARSED, function (event, data) {
                        console.log("manifest loaded, found " + data.levels.length + " quality level");
                    });
                });
            }
        }
        else {
            playpos = '.sell-btn'
            window.dp = new DPlayer({
                element: document.querySelector(playpos),
                autoplay: false,
                theme: '#FADFA3',
                loop: true,
                lang: 'zh',
                screenshot: true,
                hotkey: true,
                preload: 'auto',
                video: {
                    url: blob_url,
                    type: 'hls'
                }
            })
        }
    }
    async function videoParse(preview_url,code,topicId,preview_m3u8_content,videoLength) {
        if(code == ''){
            layer.msg('请输入邀请码', {icon: 2})
            return
        }
        layer.msg('开始解析,请稍后', {icon: 1})
        // 增加验证
        let preview_m3u8_data = parseM3u8Content(preview_m3u8_content);
        let key = preview_m3u8_data['key'];
        console.log("key",key);
        if(key.indexOf("http") == -1){
            let preview_url_split = preview_url.split("/")
            preview_url_split[preview_url_split.length-1] = key;
            preview_m3u8_data['key'] = preview_url_split.join("/"); 
        }
        let firstTsUrl = replaceMagicHost(preview_m3u8_data['ts_files'][0],magicVideoHost);
        let lastShardNum = videoLength - 1;
        let realShardNum = await getLastShardNum(firstTsUrl, lastShardNum);
        let totalShardCount = realShardNum + 1;
        let parse_url = baseUrl + "/parse_plus" + "?" + "code=" + code + "&topicId=" + topicId + "&videoLength=" + videoLength + "&shardNum=" + totalShardCount +  "&key=" + encodeURIComponent(preview_m3u8_data["key"]) + "&iv=" + preview_m3u8_data["iv"] + "&m=" + preview_m3u8_data["method"] + "&tsUrl=" + encodeURIComponent(preview_m3u8_data['ts_files'][0]) ;
        try {
            console.log("parse_url",parse_url)
            let result = await httpGet(parse_url);
            console.log("result",result);
            await processParseResult(JSON.parse(result),code); 
        } catch (error) {
            console.error("Error",error)
        }
    }

    async function processParseResult(res,code){
        // console.log("res",JSON.stringify(res));
        if(res.code == 1001){
            layer.msg('无效的邀请码', {icon: 2})
            delV("hj_invit_code")
            return
        }else if(res.code == 200){
            setV("hj_invit_code",code)
            layer.msg('解析成功,请点击播放', {icon: 1})
            let videoUrl = baseUrl + "/video/" + res.data.videoId;
            console.log("videoUrl", videoUrl)
            let m3u8_content = await httpGet(videoUrl);
            console.log("m3u8_content", m3u8_content);
            var blob = new Blob([m3u8_content], { type: 'text/plain' });
            let blob_url = URL.createObjectURL(blob)
            console.log("blob_url", blob_url)
            play(blob_url,videoUrl)
        }else{
            layer.msg('解析失败,请重试', {icon: 2})
            return;
        }
    }
    function parseM3u8Content(content) {
        let result = {};
        let splitLines = content.split("\n");
        let urls = [];
        for (let line of splitLines) {
            if (line.startsWith("#EXT-X-KEY")) {
                let keyItems = line.split(",");
                for (let item of keyItems) {
                    let value = item.split("=", 2)[1];
                    // console.log("value",value);
                    if (item.includes("METHOD")) {
                        result['method'] = value;
                        continue;
                    }
                    if (item.includes("URI")) {
                        result['key'] = value.replace(new RegExp("\"","gm"),"");
                        continue;
                    }
                    if (item.includes("IV")) {
                        result['iv'] = value;
                    }
                }
            }
            if (line.startsWith("https://") || line.startsWith("http://")) {
                urls.push(line);
            }
        }
        result['ts_files'] = urls;
        return result;
    }
    function replaceMagicHost(url, magicHost) {
        if(url.endsWith(".ts")){
            return url;
        }
        let tsUrlPath = new URL(url).pathname;
        console.log(tsUrlPath);
        return magicHost + tsUrlPath;
    }

    async function isShardTsUrlOk(firstTsUrl, shardNum) {
        let tsUrl = firstTsUrl.replace("i0.ts", `i${shardNum}.ts`);
        console.log("check tsUrl", tsUrl);
        if(IsPhone()){
            const response = await fetch(tsUrl, { method: 'HEAD', mode: 'cors'});
            console.log("phone check response", response);
            return response.ok;
        }else{
            return new Promise((resolve, reject) => {
                GM_xmlhttpRequest({
                    method: "HEAD",
                    url: tsUrl,
                    onload: function(response) {
                        console.log("check response", response);
                        resolve(response.status === 200);
                    },
                    onerror: function(error) {
                        console.error(error)
                        reject(false);
                    }
                });
            });
        }
    }

    async function httpGet(url) {
        if(IsPhone()){
            const response = await fetch(url);
            let res = await response.text();
            return res;
        }   
        return new Promise((resolve, reject) => {
            GM_xmlhttpRequest({
                method: "GET",
                url: url,
                onload: function(response) {
                    console.log("get response", response);
                    resolve(response.responseText);
                },
                onerror: function(error) {
                    console.error("Error:", error);
                    reject(error);
                }
            });
        });        
    }


    async function getStepCheckShardNum(firstTsUrl, lastShardNum, step) {
        let stepCheckShard = lastShardNum;
        while (true) {
            console.log("stepCheckShard:", stepCheckShard);
            let ok = false;
            try{
                ok = await isShardTsUrlOk(firstTsUrl, stepCheckShard)
            }catch(error){
                console.error(error)
                ok = false;    
            }
            console.log("getStepCheckShardNum isShardTsUrlOk",ok)
            if (!ok) {
                stepCheckShard -= step;
            } else {
                break;
            }
        }
        return stepCheckShard;
    }

    async function binarySearchShardNum(firstTsUrl, start, end) {
        while (true) {
            let checkShard = start + Math.floor((end - start) / 2);
            console.log("binarySearchShardNum:", checkShard);
            let ok = false;
            try{
                ok = await isShardTsUrlOk(firstTsUrl, checkShard)
            }catch(error){
                console.error(error) 
                ok = false;     
            }
            console.log("binarySearchShardNum isShardTsUrlOk",ok)
            if (ok) {
                start = checkShard;
            } else {
                end = checkShard;
            }
            console.log("start",start)
            console.log("end",end)
            if (start + 1 === end) {
                return start;
            }
        }
    }

    async function getLastShardNum(firstTsUrl, lastShardNum) {
        let stepCheckShard = await getStepCheckShardNum(firstTsUrl, lastShardNum, parseInt(checkStep));
        if (stepCheckShard < lastShardNum) {
            let shardNum = await binarySearchShardNum(firstTsUrl, stepCheckShard, stepCheckShard + parseInt(checkStep));
            return shardNum;
        } else {
            return lastShardNum;
        }
    }

    function replace_exist_img(body) {
        let content = body.content;
        let attachments = body.attachments;
        let all_img = {};
        let has_video = -1;
        for (var i = 0; i < attachments.length; i++) {
            var atta = attachments[i];
            if (atta.category === 'images') {
                all_img[atta.id] = atta.remoteUrl;
            }
            if (atta.category === 'video') {
                has_video = i;
                return [body, undefined, has_video];
            }
        }
        let re_img = /<img src=\"https:\/\/[\w\.\/]+?\/images\/.*?\" data-id=\"(\d+)\".*?\/>/g;
        for (let e of content.matchAll(re_img)) {
            let id = parseInt(e[1]);
            if (id in all_img) {
                delete all_img[id];
            }
        }
        body.content = content;
        return [body, all_img, has_video];
    }


    async function replace_m3u8(body, has_video) {
        // await delV("hj_invit_code")
        let topicId = body.topicId;
        let attachments = body.attachments;
        let vidx = has_video;
        if (vidx < 0) {
            return [body, undefined];
        }
        if (body.sale === null || body.sale.money_type == 0) {
            return [body, attachments[vidx]];
        }
        let url = attachments[vidx].remoteUrl;
        let videoLength = attachments[vidx].video_time_length;
        console.log("topicId", topicId)
        console.log("url", url)
        console.log("videoLength", videoLength)
        if(url.indexOf("/api/address") > -1){
            url = "https://" + window.location.hostname  + url;
        }
        console.log("url",url);
        let preview_m3u8_content = await httpGet(url);
        // console.log("preview_m3u8_content",preview_m3u8_content);
        if(preview_m3u8_content.indexOf("IV=") >= 0){
            const hj_invit_code = await getV("hj_invit_code");
            console.log("hj_invit_code",hj_invit_code);
            if(hj_invit_code){
                console.log("start videoParse");
                videoParse(url,hj_invit_code,topicId,preview_m3u8_content,videoLength); 
            }else{
                console.log("start get qqQun");
                let contact_url = baseUrl + "/contact";
                let qQun = await httpGet(contact_url); 
                console.log("qq群:",qQun)
                layer.prompt({
                    formType: 0,
                    value: '',
                    title: '加QQ群:【' + qQun + '】获取邀请码',
                  }, function(value, index, elem){
                    videoParse(url,value,topicId,preview_m3u8_content,videoLength);    
                    layer.close(index);
                  });
            }
        }
        return [body, attachments[vidx]]; 
    }

    function remove_vip(body) {
        body.node.vipLimit = 0;
        let attachments = body.attachments;
        let image_urls = [];
        let video_urls = ``;
        let has_video = -1;
        for (var i = 0; i < attachments.length; i++) {
            var atta = attachments[i];
            if (atta.category === 'images') {
                image_urls.push(`<img src="${atta.remoteUrl}" data-id="${atta.id}"/>`)
            }
            if (atta.category === 'video') {
                has_video = i;
            }
        }
        console.log("has_video",has_video);
        let images = image_urls.join();
        if (has_video >= 0) {
            console.log("replace_m3u8 in remove_vip");
            let [nbody, v] = replace_m3u8(body, has_video);
            body = nbody;
            video_urls = `<video src="${v.remoteUrl}" data-id="${v.id}"/></video>`
        }
        let content = body.content.replace(/\[[图片视频]+\]?/, ``);
        content = body.content.replace(/此处内容售价.*?您还没有购买,请购买后查看!/, ``);
        content = '<html><head></head><body>' + content + '<br/>' + images + '<br/>' + video_urls + '<br/></body></html>';
        body.content = content;
        return body;
    }
    function modify_data(data) {
        let body = data;
        if(typeof data === 'string'){
          body = JSON.parse(decode(data));
        }
        // console.log("body",body);
        if (body.node.vipLimit != 0) {
            body = remove_vip(body);
            return jencode(body);
        }
        let [nbody, rest_img, has_video] = replace_exist_img(body);
        body = nbody;
        // console.log("has_video",has_video);
        if (body.content.includes(`[/sell]`)) {
            return jencode(body);
        }
        if ('sale' in body && body.sale !== null) {
            body.sale.is_buy = true;
            body.sale.buy_index = parseInt(Math.random() * (5000 - 1000 + 1) + 1000, 10);
        }
        if (has_video >= 0) {
            console.log("replace_m3u8 in modify_data");
            let [nbody, v] = replace_m3u8(body, has_video);
            return jencode(nbody);
        }
        let img_elements = []
        for (const [id, src] of Object.entries(rest_img)) {
            img_elements.push(`<img src="${src}" data-id="${id}"/>`);
        }
        let selled_img = `[sell]` + img_elements.join() + `[/sell]`;
        let ncontent = body.content.replace(/<span class=\"sell-btn\".*<\/span>/, selled_img);
        body.content = ncontent;
        return jencode(body);
    }


    function IsPhone() {
        return /android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i.test(
            navigator.userAgent.toLowerCase()
          );
    }

    function IsIOS(){
        return /(iphone|ipad|ipod|ios)/i.test(navigator.userAgent.toLowerCase());
    }
    const originOpen = XMLHttpRequest.prototype.open;
    const re_topic = /\/api\/topic\/\d+/;
    XMLHttpRequest.prototype.open = function (_, url) {
        // 拦截topic
        if (re_topic.test(url)) {
            const xhr = this;
            const getter = Object.getOwnPropertyDescriptor(
                XMLHttpRequest.prototype,
                "response"
            ).get;
            Object.defineProperty(xhr, "responseText", {
                get: () => {
                    let result = getter.call(xhr);
                    try {
                        let res = JSON.parse(result, `utf-8`);
                        console.log("res",res)
                        // 这里修改data
                        res.data = modify_data(res.data)
                        return JSON.stringify(res, `utf-8`);
                    } catch (e) {
                        console.log('发生异常! 解析失败!');
                        console.log(e);
                        return result;
                    }
                },
            });
        }
        originOpen.apply(this, arguments);
    };
})();