添加 haijiao/haijiao.user.js
This commit is contained in:
377
haijiao/haijiao.user.js
Normal file
377
haijiao/haijiao.user.js
Normal file
@@ -0,0 +1,377 @@
|
|||||||
|
// ==UserScript==
|
||||||
|
// @name 海角社区m3u8提取+去广告-原位替换终极播放版(binghe修改版)
|
||||||
|
// @namespace haijiao-analyst
|
||||||
|
// @version 4.0
|
||||||
|
// @author binghe修改版
|
||||||
|
// @description (手机版) 1.自动屏蔽广告 2.捕获m3u8 3.内置HLS引擎+强力播放器节点注入兜底
|
||||||
|
// @license MIT
|
||||||
|
// @match *://*.haijiao.com/*
|
||||||
|
// @match *://*/post/details*
|
||||||
|
// @grant GM_addStyle
|
||||||
|
// @grant unsafeWindow
|
||||||
|
// @run-at document-start
|
||||||
|
// ==/UserScript==
|
||||||
|
|
||||||
|
(function () {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
let capturedM3u8Url = '';
|
||||||
|
let hasReplaced = false;
|
||||||
|
const btnId = 'hj-play-btn-mobile';
|
||||||
|
|
||||||
|
// ==========================================
|
||||||
|
// 模块一:强力去广告 (静态数据清洗)
|
||||||
|
// ==========================================
|
||||||
|
const adStyles = `
|
||||||
|
[class*="guanggao"], [class*="ads"], [id*="ads"],
|
||||||
|
[class*="banner"], [id*="banner"],
|
||||||
|
.float-box, .float-window, .couplet,
|
||||||
|
.top-ad, .bottom-ad, .sidebar,
|
||||||
|
.ad-container, .gg-box,
|
||||||
|
iframe:not([src*="m3u8"]):not([src*="player"]):not([id*="play"]),
|
||||||
|
.ad-box img, a[href*="bocai"]
|
||||||
|
{
|
||||||
|
display: none !important;
|
||||||
|
visibility: hidden !important;
|
||||||
|
height: 0 !important;
|
||||||
|
width: 0 !important;
|
||||||
|
opacity: 0 !important;
|
||||||
|
pointer-events: none !important;
|
||||||
|
}
|
||||||
|
body { overflow-x: hidden; }
|
||||||
|
`;
|
||||||
|
GM_addStyle(adStyles);
|
||||||
|
|
||||||
|
function cleanUpAds() {
|
||||||
|
document.querySelectorAll('div[style*="z-index: 999"]').forEach(div => div.remove());
|
||||||
|
document.querySelectorAll('div[style*="position: fixed"]').forEach(div => {
|
||||||
|
if (div.id !== btnId && div.id !== 'hj-toast-mobile') {
|
||||||
|
if (div.querySelector('iframe') || div.querySelector('img')) {
|
||||||
|
div.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setInterval(cleanUpAds, 2000);
|
||||||
|
|
||||||
|
// ==========================================
|
||||||
|
// 模块二:数据下行拦截 (拦截真实 M3U8 地址)
|
||||||
|
// ==========================================
|
||||||
|
GM_addStyle(`
|
||||||
|
#${btnId} {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 80px;
|
||||||
|
right: 15px;
|
||||||
|
width: 55px;
|
||||||
|
height: 55px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: rgba(30, 60, 114, 0.95);
|
||||||
|
color: white;
|
||||||
|
border: 2px solid rgba(255,255,255,0.3);
|
||||||
|
z-index: 999999;
|
||||||
|
font-size: 24px;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 51px;
|
||||||
|
user-select: none;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
display: none;
|
||||||
|
box-shadow: 0 4px 10px rgba(0,0,0,0.4);
|
||||||
|
transition: transform 0.2s, background-color 0.3s;
|
||||||
|
}
|
||||||
|
#${btnId}.show { display: block; }
|
||||||
|
#${btnId}.active { transform: scale(0.9); }
|
||||||
|
#${btnId}.played { background-color: rgba(46, 139, 87, 0.95); }
|
||||||
|
|
||||||
|
#hj-toast-mobile {
|
||||||
|
position: fixed;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
background: rgba(0,0,0,0.8);
|
||||||
|
color: #fff;
|
||||||
|
padding: 12px 24px;
|
||||||
|
border-radius: 8px;
|
||||||
|
z-index: 1000000;
|
||||||
|
font-size: 15px;
|
||||||
|
pointer-events: none;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.3s;
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
|
const getRealVideoSrc = (content, requestUrl) => {
|
||||||
|
if (!content) return "";
|
||||||
|
try {
|
||||||
|
if (content.includes("#EXTM3U")) {
|
||||||
|
const baseUrl = requestUrl.substring(0, requestUrl.lastIndexOf('/') + 1);
|
||||||
|
const filenameMatch = content.match(/([\w_]+_?)[\d]+\.ts/);
|
||||||
|
if (filenameMatch) return baseUrl + filenameMatch[1] + ".m3u8";
|
||||||
|
} else {
|
||||||
|
const lines = content.split("\n");
|
||||||
|
const tsLine = lines.find(line => line.includes('.ts'));
|
||||||
|
if (tsLine) {
|
||||||
|
let reg = tsLine.match(/([\w_]+_?)[\d]+\.ts/);
|
||||||
|
if (reg) return tsLine.replace(reg[0], reg[1] + ".m3u8").trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) { }
|
||||||
|
return "";
|
||||||
|
};
|
||||||
|
|
||||||
|
const bareDecode = (text) => { try { return JSON.parse(atob(atob(atob(text)))); } catch (e) { return null; } };
|
||||||
|
const decodeEncryptString = (text) => {
|
||||||
|
let data = text;
|
||||||
|
try {
|
||||||
|
if (typeof text === 'string') {
|
||||||
|
let tmpDataObj = null;
|
||||||
|
try { tmpDataObj = JSON.parse(text); } catch (e) { }
|
||||||
|
if (tmpDataObj) {
|
||||||
|
if (tmpDataObj.data && typeof tmpDataObj.data === 'object') {
|
||||||
|
data = tmpDataObj.data;
|
||||||
|
} else if (tmpDataObj.data && typeof tmpDataObj.data === 'string') {
|
||||||
|
data = bareDecode(tmpDataObj.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) { }
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const originalXHR = unsafeWindow.XMLHttpRequest;
|
||||||
|
unsafeWindow.XMLHttpRequest = function () {
|
||||||
|
const xhr = new originalXHR();
|
||||||
|
const originalOpen = xhr.open;
|
||||||
|
const originalSend = xhr.send;
|
||||||
|
let requestUrl = '';
|
||||||
|
xhr.open = function (method, url) {
|
||||||
|
requestUrl = url || '';
|
||||||
|
return originalOpen.apply(this, arguments);
|
||||||
|
};
|
||||||
|
xhr.send = function () {
|
||||||
|
if (requestUrl.includes("/api/address/") || requestUrl.includes("/api/topic/")) {
|
||||||
|
xhr.addEventListener('load', function () {
|
||||||
|
setTimeout(() => handleXhrLoad(xhr, requestUrl), 0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return originalSend.apply(this, arguments);
|
||||||
|
};
|
||||||
|
return xhr;
|
||||||
|
};
|
||||||
|
|
||||||
|
async function handleXhrLoad(xhr, url) {
|
||||||
|
try {
|
||||||
|
if (url.includes("/api/address/")) {
|
||||||
|
const videoSrc = getRealVideoSrc(xhr.responseText, url);
|
||||||
|
if (videoSrc) updateState(videoSrc);
|
||||||
|
}
|
||||||
|
else if (/\/api\/topic\/\d+/.test(url)) {
|
||||||
|
const data = decodeEncryptString(xhr.responseText);
|
||||||
|
if (data?.attachments) {
|
||||||
|
for (const element of data.attachments) {
|
||||||
|
if (element.category === "video" && element.remoteUrl) {
|
||||||
|
fetch(element.remoteUrl)
|
||||||
|
.then(res => res.text())
|
||||||
|
.then(content => {
|
||||||
|
const videoSrc = getRealVideoSrc(content, element.remoteUrl);
|
||||||
|
if (videoSrc) updateState(videoSrc);
|
||||||
|
}).catch(() => { });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateState(src) {
|
||||||
|
if (!src || src === capturedM3u8Url) return;
|
||||||
|
capturedM3u8Url = src;
|
||||||
|
showToast('已捕获完整 M3U8 索引,正在自动替换播放器...');
|
||||||
|
// 自动替换播放器,无需手动点击
|
||||||
|
if (!hasReplaced) {
|
||||||
|
replaceAndPlayVideo();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showToast(msg) {
|
||||||
|
let toast = document.getElementById('hj-toast-mobile');
|
||||||
|
if (!toast) {
|
||||||
|
toast = document.createElement('div');
|
||||||
|
toast.id = 'hj-toast-mobile';
|
||||||
|
document.body.appendChild(toast);
|
||||||
|
}
|
||||||
|
toast.textContent = msg;
|
||||||
|
toast.style.opacity = '1';
|
||||||
|
setTimeout(() => { toast.style.opacity = '0'; }, 3000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==========================================
|
||||||
|
// 模块三:高级解析节点注入与 M3U8 流媒体接管
|
||||||
|
// ==========================================
|
||||||
|
function replaceAndPlayVideo() {
|
||||||
|
if (hasReplaced) return;
|
||||||
|
hasReplaced = true;
|
||||||
|
showToast('开始启动数据管线与播放器构筑...');
|
||||||
|
|
||||||
|
// 【1】暴力清洗阶段:寻址页面现有的全部正在播放的原生媒体,将其静音并断肠式销毁
|
||||||
|
document.querySelectorAll('video').forEach(v => {
|
||||||
|
v.pause();
|
||||||
|
v.removeAttribute('src');
|
||||||
|
v.load();
|
||||||
|
const potentialParent = v.closest('.video-div') || v.closest('.dplayer') || v.parentElement;
|
||||||
|
if (potentialParent) potentialParent.innerHTML = ''; // 清空它原来的播放外壳
|
||||||
|
});
|
||||||
|
|
||||||
|
// 隐藏那些“付费提示”或“只有30秒预览”等脏数据文案
|
||||||
|
document.querySelectorAll('.sell_line1, .sell_line2, .preview-title').forEach(el => {
|
||||||
|
el.style.display = 'none';
|
||||||
|
});
|
||||||
|
|
||||||
|
// 【2】模糊寻址阶段:采用多维度的探针去锁定可能作为注入地点的容器
|
||||||
|
// 选择器兼容:原始特征、ID特征、购买容器特征
|
||||||
|
let videoContainers = document.querySelectorAll('.video-div, .dplayer, [id^="video_"], .sell-btn, .post-details');
|
||||||
|
|
||||||
|
let targetContainer = null;
|
||||||
|
|
||||||
|
if (videoContainers.length > 0) {
|
||||||
|
targetContainer = videoContainers[0]; // 抓住锁定的第一个点位
|
||||||
|
console.log("【数据洞察】常规寻址成功");
|
||||||
|
} else {
|
||||||
|
// 【3】主动兜底机制 (Proactive Fallback):节点异形变幻到无法寻址?
|
||||||
|
// 我们直接凭空生造一个 div 插入在最前面!
|
||||||
|
console.log("【数据洞察】常规寻址脱靶,触发降级注入策略!");
|
||||||
|
showToast('启用兜底注入模式,强制渲染播放器');
|
||||||
|
|
||||||
|
targetContainer = document.createElement('div');
|
||||||
|
targetContainer.style.margin = '15px 0';
|
||||||
|
let anchorNode = document.querySelector('body');
|
||||||
|
|
||||||
|
// 尝试挂载到内容顶部附近
|
||||||
|
let contentBody = document.querySelector('.post-content') || document.querySelector('.article-content') || document.body;
|
||||||
|
contentBody.insertBefore(targetContainer, contentBody.firstChild);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化容器 (如果是老的Dplayer容器,就把里面的脏DOM连根拔起)
|
||||||
|
targetContainer.innerHTML = '';
|
||||||
|
const newPlayerId = 'clean-core-player-' + Date.now();
|
||||||
|
|
||||||
|
// 【提取页面标题】
|
||||||
|
let pageTitle = '未知标题';
|
||||||
|
const titleSpan = document.querySelector('.header h2 > span');
|
||||||
|
if (titleSpan) {
|
||||||
|
pageTitle = titleSpan.textContent.trim();
|
||||||
|
} else if (document.title) {
|
||||||
|
pageTitle = document.title.trim();
|
||||||
|
}
|
||||||
|
console.log('[Clean Player] 提取到页面标题:', pageTitle);
|
||||||
|
|
||||||
|
// 挂载全新的展示区(一体化无缝卡片设计)
|
||||||
|
targetContainer.innerHTML = `
|
||||||
|
<div style="margin: 0 auto; width: 100%; border-radius: 12px; overflow: hidden; box-shadow: 0 12px 32px rgba(0,0,0,0.9); background-color: #000; border: 1px solid #2a2a2a; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;">
|
||||||
|
<div style="margin: 0; padding: 14px 18px; background: #18181c; border-bottom: 1px solid #0d0d0f; display: flex; flex-direction: column; gap: 8px;">
|
||||||
|
<div style="font-size: 16px; font-weight: 600; color: #ececec; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; letter-spacing: 0.3px;">
|
||||||
|
${pageTitle}
|
||||||
|
</div>
|
||||||
|
<div style="font-size: 12px; color: #10b981; display: flex; align-items: center; font-weight: 500;">
|
||||||
|
<span style="display:inline-block; width: 6px; height: 6px; background-color: #10b981; border-radius: 50%; margin-right: 8px; box-shadow: 0 0 8px rgba(16,185,129,0.8);"></span>
|
||||||
|
<span style="opacity: 0.9;">Analytics 探针激活 · 广告及播放限制已彻底解除 · M3U8 直连就绪</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<video
|
||||||
|
id="${newPlayerId}"
|
||||||
|
controls
|
||||||
|
playsinline
|
||||||
|
webkit-playsinline
|
||||||
|
style="display: block; width: 100%; max-height: 80vh; background-color: #000; margin: 0; padding: 0; border: none; outline: none;">
|
||||||
|
</video>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
const newVideoTag = targetContainer.querySelector(`#${newPlayerId}`);
|
||||||
|
const targetM3u8 = capturedM3u8Url;
|
||||||
|
|
||||||
|
// 【4】M3U8 智能解析路由
|
||||||
|
if (newVideoTag.canPlayType('application/vnd.apple.mpegurl')) {
|
||||||
|
// 场景 1: 原生硬解 (iOS)
|
||||||
|
newVideoTag.src = targetM3u8;
|
||||||
|
showToast('iOS 原生硬解就绪,点击播放');
|
||||||
|
} else {
|
||||||
|
// 场景 2: 软解加载 (Android / PC)
|
||||||
|
const GlobalHls = unsafeWindow.Hls || window.Hls;
|
||||||
|
|
||||||
|
if (GlobalHls && GlobalHls.isSupported()) {
|
||||||
|
let hls = new GlobalHls();
|
||||||
|
hls.loadSource(targetM3u8);
|
||||||
|
hls.attachMedia(newVideoTag);
|
||||||
|
hls.on(GlobalHls.Events.MANIFEST_PARSED, function () {
|
||||||
|
console.log('HLS 解析完毕,播放器就绪');
|
||||||
|
});
|
||||||
|
showToast('HLS 引擎解码就绪,点击播放');
|
||||||
|
} else {
|
||||||
|
showToast('主动下载边缘节点 M3U8 解析器...');
|
||||||
|
let script = document.createElement('script');
|
||||||
|
script.src = 'https://cdn.jsdelivr.net/npm/hls.js@latest';
|
||||||
|
script.onload = () => {
|
||||||
|
const InjectedHls = unsafeWindow.Hls || window.Hls;
|
||||||
|
if (InjectedHls && InjectedHls.isSupported()) {
|
||||||
|
let hls = new InjectedHls();
|
||||||
|
hls.loadSource(targetM3u8);
|
||||||
|
hls.attachMedia(newVideoTag);
|
||||||
|
hls.on(InjectedHls.Events.MANIFEST_PARSED, () => {
|
||||||
|
console.log('外置 HLS 引擎解析完毕,播放器就绪');
|
||||||
|
showToast('外置引擎挂载完毕,点击播放');
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
showToast('您的设备底层解码器版本不兼容。');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
document.head.appendChild(script);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const btn = document.getElementById(btnId);
|
||||||
|
if (btn) {
|
||||||
|
btn.classList.add('played');
|
||||||
|
btn.textContent = '✔️';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
if (document.getElementById(btnId)) return;
|
||||||
|
const btn = document.createElement('div');
|
||||||
|
btn.id = btnId;
|
||||||
|
btn.textContent = '▶️';
|
||||||
|
|
||||||
|
btn.onclick = function () {
|
||||||
|
btn.classList.add('active');
|
||||||
|
setTimeout(() => btn.classList.remove('active'), 100);
|
||||||
|
|
||||||
|
if (capturedM3u8Url) {
|
||||||
|
// 备用手动触发(若自动替换未执行)
|
||||||
|
hasReplaced = false;
|
||||||
|
replaceAndPlayVideo();
|
||||||
|
} else {
|
||||||
|
showToast('后台尚未捕获到完整数据的索引参数,请等待刷新或播放原视频探寻。');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
document.body.appendChild(btn);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (document.readyState === 'loading') {
|
||||||
|
window.addEventListener('DOMContentLoaded', init);
|
||||||
|
} else {
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('popstate', () => {
|
||||||
|
capturedM3u8Url = '';
|
||||||
|
hasReplaced = false;
|
||||||
|
const btn = document.getElementById(btnId);
|
||||||
|
if (btn) {
|
||||||
|
btn.classList.remove('show');
|
||||||
|
btn.classList.remove('played');
|
||||||
|
btn.textContent = '▶️';
|
||||||
|
}
|
||||||
|
setTimeout(cleanUpAds, 500);
|
||||||
|
});
|
||||||
|
|
||||||
|
})();
|
||||||
Reference in New Issue
Block a user