// ==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(`
`)
}else{
playpos = '.sell-btn'
let videoInfo = document.querySelector(playpos)
videoInfo.innerHTML = ''
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 = //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: '下方输入兑换码VIP999:【' + 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(`
`)
}
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 = ``
}
let content = body.content.replace(/\[[图片视频]+\]?/, ``);
content = body.content.replace(/此处内容售价.*?您还没有购买,请购买后查看!/, ``);
content = '