Files
UserScript/haijiao/haijiao.user.js

378 lines
15 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// ==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);
});
})();