インストールの前に、Sleazy Forkは、このスクリプトにアンチ機能が含まれることをお知らせします。これはあなたではなく、スクリプトの作者の利益を目的としてます。
このスクリプトは支払うことでのみ完全な機能となります。Sleazy Fork は支払いに関与しないので、価値のあるものを入手できるかを検証していませんし、返金のお手伝いもできないので注意してください。
🚀新功能上线,不浪费一秒,会员随时停启💰。付费内容无限次观看、便捷下载和即时分享链接。受益于保存的登录信息、媒体帖子视觉提示(红色标题)、内容自动展开和完全无广告的环境🚀。
// ==UserScript== // @name 海角社区 // @name:zh-TW 海角社區 // @namespace http://tampermonkey.net/ // @version v1.9 // @description 🚀新功能上线,不浪费一秒,会员随时停启💰。付费内容无限次观看、便捷下载和即时分享链接。受益于保存的登录信息、媒体帖子视觉提示(红色标题)、内容自动展开和完全无广告的环境🚀。 // @description:zh-TW 🚀新功能上線,不浪費一秒,會員隨時停啟💰。付費內容無限次觀看、便捷下載和即時分享連結受益於保存的登錄信息、媒體帖子視覺提示(紅色標題)、內容自動展開和完全無廣告的環境🚀。 // @author lx // @match https://hjcx.org/* // @match https://hjcx.org/* // @match https://www.huajitv.com/* // @match https://huajitv.com/* // @match https://www.haijiao.com/* // @match https://haijiao.com/* // @match https://haijiao.ai/* // @match */post/details?pid=* // @exclude *://www.haijiaom.cc/* // @run-at document-start // @grant unsafeWindow // @grant GM_addStyle // @grant GM_getValue // @grant GM_setValue // @grant GM_xmlhttpRequest // @connect haijiao.live // @charset UTF-8 // @antifeature payment // @license MIT // @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw== // ==/UserScript== (function () { let baseUrl = "https://www.haijiaom.cc/api/app-api"; // let baseUrl = "http://127.0.0.1:8080/api/app-api"; let vipUser = {}; let isPlaying = false; let siteCode = "HJSQ"; let sDataId = ""; let slefVideo = { videoPlayUrl: "", videoDuration: 0, id: 0, articleId: 0, videoBaseUrl: "", videoArticleId: "", downloadUrl: "", }; let hjsqUserKye = "hjsq_gm_key"; let baseDownadloadUrl = baseUrl + "/business/download/down.m3u8?token="; let m3u8Url = ""; let hasPrev = false; waitForPageLoad() .then((body) => { fetchUserInfo(); createNavbar(body); removeAdds(); initHlsPlayer(); }) .catch((error) => { console.error("Failed to load page:", error); }); //创建导航栏 function createNavbar(body) { const fontAwesome = document.createElement("link"); fontAwesome.rel = "stylesheet"; fontAwesome.href = "https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css"; document.head.appendChild(fontAwesome); const navbar = document.createElement("div"); navbar.id = "custom-navbar"; navbar.style.cssText = ` position: fixed; top: 20%; right:-20px; transform: translate(-50%, -50%); background: rgba(255, 255, 255, 0); padding: 15px; border-radius: 12px; box-shadow: 0 4px 15px rgba(0,0,0,0.15); transition: all 0.3s ease; z-index: 99999; `; const buttonsContainer = document.createElement("div"); buttonsContainer.style.cssText = ` display: flex; flex-direction: column; gap: 20px; `; // Add event.preventDefault() to button actions const buttons = [ { id: "personalInfoBtn", icon: "fas fa-user", action: (e) => { e.preventDefault(); createPersonalInfoDialog(); }, }, { id: "downloadBtn", icon: "fas fa-download", action: (e) => { e.preventDefault(); createDownloadDialog(); }, }, { id: "playBtn", icon: "fas fa-play", action: (e) => { e.preventDefault(); togglePlay(); }, }, { id: "toggleBtn", icon: "fas fa-eye-slash", action: (e) => { e.preventDefault(); toggleVisibility(); }, }, ]; const buttonStyle = ` width: 20px; height: 20px; border: none; border-radius: 50%; background: rgb(0, 0, 0,0.2); color: white; cursor: pointer; transition: all 0.2s ease; display: flex; align-items: center; justify-content: center; font-size: 16px; `; buttons.forEach((btn) => { const button = document.createElement("button"); button.id = btn.id; button.innerHTML = `<i class="${btn.icon}"></i>`; button.style.cssText = buttonStyle; button.addEventListener("click", btn.action); buttonsContainer.appendChild(button); }); navbar.appendChild(buttonsContainer); body.appendChild(navbar); // Floating toggle button const floatingBtn = document.createElement("button"); floatingBtn.id = "floating-toggle-btn"; floatingBtn.style.cssText = ` position: fixed; bottom: 20px; right: 20px; width: 20px; height: 20px; border: none; border-radius: 50%; background: #4a90e2; color: white; cursor: pointer; transition: all 0.2s ease; display: none; align-items: center; justify-content: center; font-size: 12px; z-index: 9998; `; floatingBtn.innerHTML = '<i class="fas fa-eye"></i>'; floatingBtn.addEventListener("click", toggleVisibility); document.body.appendChild(floatingBtn); } // 等待页面加载完成 function createLoginFormView() { const loginDialog = document.createElement("div"); let existingDialog = document.querySelector('[id^="login-dialog"]'); if (existingDialog) return; loginDialog.innerHTML = ` <style> .login-container { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: white; padding: 30px; border-radius: 12px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); width: 90%; max-width: 400px; box-sizing: border-box; } .login-input { width: 100%; padding: 12px; border: 1px solid #ddd; border-radius: 6px; font-size: 14px; box-sizing: border-box; } .login-btn { width: 100%; padding: 12px; background: #4a90e2; color: white; border: none; border-radius: 6px; font-size: 15px; cursor: pointer; } .login-error { color: #ff4d4f; font-size: 13px; margin-top: 8px; } @media (max-width: 480px) { .login-container { padding: 20px; width: 70%; } h2 { font-size: 20px !important; margin-bottom: 8px !important; } p { font-size: 13px hai; } .login-input { padding: 10px; font-size: 13px; } .login-btn { padding: 10px; font-size: 14px; } .home-link { text-align: center; margin-top: 15px; } .home-link a { color: #4a90e2; text-decoration: none; font-size: 14px; } .home-link a:hover { text-decoration: underline; } } </style> <div class="login-container" id="login-dialog"> <div style="text-align: right;"> <span class="dialog-close" style="cursor: pointer; font-size: 18px;">×</span> </div> <div style="margin-bottom: 20px;"> <h2 style="margin: 0 0 8px; color: #333; font-size: 22px; font-weight: 600;">欢迎登录</h2> <p style="margin: 0; color: #666; font-size: 14px;">请输入您的账号密码</p> </div> <div style="margin-bottom: 15px;"> <input type="text" id="username" placeholder="用户名" class="login-input"> </div> <div style="margin-bottom: 20px;"> <input type="password" id="password" placeholder="密码" class="login-input"> </div> <button id="loginSubmit" class="login-btn"> <span id="loginText">登 录</span> <span id="loginLoading" style="display:none;"> <i class="fas fa-spinner fa-spin"></i> </span> </button> <div class="home-link"> <a href="https://www.haijiaom.cc" id="goHome"> <i class="fas fa-home"></i> 会员注册 </a> </div> <div id="loginError" class="login-error"></div> </div> `; document.body.appendChild(loginDialog); // Make login dialog responsive // Close dialog when clicking X document.querySelector(".dialog-close").addEventListener("click", () => { loginDialog.remove(); }); // 添加输入框焦点效果 const inputs = loginDialog.querySelectorAll("input"); inputs.forEach((input) => { input.addEventListener("focus", () => { input.style.borderColor = "#4a90e2"; }); input.addEventListener("blur", () => { input.style.borderColor = "#ddd"; }); }); document .getElementById("loginSubmit") .addEventListener("click", async () => { const username = document.getElementById("username").value; const password = document.getElementById("password").value; const loginBtn = document.getElementById("loginSubmit"); const loginText = document.getElementById("loginText"); const loginLoading = document.getElementById("loginLoading"); loginText.style.display = "none"; loginLoading.style.display = "inline-block"; loginBtn.disabled = true; if (!username || !password) { const errorDiv = document.getElementById("loginError"); errorDiv.textContent = "请输入用户名和密码"; errorDiv.style.display = "block"; return; } try { const response = await fetch(baseUrl + "/business/member/login", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ username, password }), }); const data = await response.json(); if (data.code == 200 && data.data.access_token) { GM_setValue("myToken", data.data.access_token); // localStorage.setItem("token", data.accessToken); loginDialog.remove(); } else { throw new Error(data.msg || "登录失败"); } } catch (error) { const errorDiv = document.getElementById("loginError"); errorDiv.textContent = error.message; errorDiv.style.display = "block"; loginText.style.display = "inline-block"; loginLoading.style.display = "none"; loginBtn.disabled = false; } }); } function createLoginForm() { // 防止重复创建 if (document.getElementById("login-container-unique-v8")) { return; } // 创建主容器 const container = document.createElement("div"); container.id = "login-container-unique-v8"; container.innerHTML = ` <style> @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600&display=swap'); #login-container-unique-v8 { all: initial !important; position: fixed !important; top: 0 !important; left: 0 !important; width: 100% !important; height: 100% !important; background: transparent !important; display: flex !important; justify-content: center !important; align-items: center !important; z-index: 2147483647 !important; font-family: 'Poppins', sans-serif !important; font-size: 12px !important; opacity: 0; transition: opacity 0.5s ease !important; } #login-container-unique-v8 * { font-family: inherit !important; box-sizing: border-box !important; } .login-box-v8 { background: rgba(75, 75, 214, 0.85) !important; backdrop-filter: blur(10px) !important; -webkit-backdrop-filter: blur(10px) !important; border-radius: 20px !important; border: 1px solid rgba(255, 255, 255, 0.2) !important; box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.37) !important; padding: 16px 40px !important; width: 60% !important; max-width: 400px !important; text-align: center !important; color: white !important; position: relative !important; transform: scale(0.9); transition: transform 0.5s ease !important; } .login-title-v8 { font-size: 16px !important; font-weight: 600 !important; margin-bottom: 32px !important; } .input-group-v8 { position: relative !important; margin-bottom: 24px !important; } .login-input-v8 { width: 100% !important; padding: 6px 6px 6px 48px !important; border: 1px solid rgba(255, 255, 255, 0.3) !important; border-radius: 10px !important; font-size: 12px !important; background: rgba(255, 255, 255, 0.2) !important; color: white !important; transition: background-color 0.3s, box-shadow 0.3s !important; } .login-input-v8::placeholder { color: rgba(255, 255, 255, 0.7) !important; } .login-input-v8:focus { outline: none !important; background: rgba(255, 255, 255, 0.3) !important; box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.5) !important; } .input-icon-v8 { position: absolute !important; left: 16px !important; top: 50% !important; transform: translateY(-50%) !important; color: rgba(255, 255, 255, 0.8) !important; width: 20px !important; height: 20px !important; } .login-button-v8 { width: 100% !important; padding: 6px !important; border: none !important; border-radius: 10px !important; background: #ff6b6b !important; color: white !important; font-size: 18px !important; font-weight: 600 !important; cursor: pointer !important; transition: background-color 0.3s, transform 0.2s !important; margin-bottom: 16px !important; /* 为下方的链接留出空间 */ } .login-button-v8:hover { background: #ff4757 !important; transform: translateY(-2px) !important; } .close-button-v8 { position: absolute !important; top: 15px !important; right: 15px !important; background: none !important; border: none !important; font-size: 16px !important; color: rgba(255, 255, 255, 0.8) !important; cursor: pointer !important; transition: color 0.3s, transform 0.3s !important; line-height: 1 !important; padding: 0 !important; } .close-button-v8:hover { color: white !important; transform: rotate(90deg) !important; } /* 新增: 注册链接区域样式 */ .extra-links-v8 { font-size: 12px !important; color: rgba(255, 255, 255, 0.8) !important; } .register-link-v8 { color: #ff8c8c !important; text-decoration: none !important; font-weight: 600 !important; cursor: pointer !important; transition: color 0.3s !important; } .register-link-v8:hover { text-decoration: underline !important; color: #ffffff !important; } .error-message-v8 { color: #ffcdd2 !important; font-size: 14px !important; text-align: left !important; margin-top: -16px !important; /* 放在输入框和按钮之间 */ margin-bottom: 16px !important; min-height: 21px !important; /* 预留空间防止布局抖动 */ visibility: hidden; opacity: 0; transition: opacity 0.3s ease-in-out !important; } .error-message-v8.visible { visibility: visible; opacity: 1; } </style> <div class="login-box-v8"> <button class="close-button-v8">×</button> <h2 class="login-title-v8">欢迎回来</h2> <div class="input-group-v8"> <svg class="input-icon-v8" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M10 2a5 5 0 00-5 5v2a2 2 0 00-2 2v5a2 2 0 002 2h10a2 2 0 002-2v-5a2 2 0 00-2-2V7a5 5 0 00-5-5zm0 2.5a2.5 2.5 0 012.5 2.5V7h-5V7a2.5 2.5 0 012.5-2.5z"></path></svg> <input type="text" class="login-input-v8" placeholder="用户名"> </div> <div class="input-group-v8"> <svg class="input-icon-v8" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z" clip-rule="evenodd"></path></svg> <input type="password" class="login-input-v8" placeholder="密码"> </div> <p class="error-message-v8"></p> <button class="login-button-v8">登录</button> <!-- 新增: 注册链接 --> <div class="extra-links-v8"> <span>没有账户? <a class="register-link-v8">立即注册</a></span> </div> </div> `; document.body.appendChild(container); const loginBox = container.querySelector(".login-box-v8"); const errorElement = container.querySelector(".error-message-v8"); requestAnimationFrame(() => { container.style.opacity = "1"; if (loginBox) { loginBox.style.transform = "scale(1)"; } }); const showError = (message) => { errorElement.textContent = message; errorElement.classList.add("visible"); }; const hideError = () => { errorElement.classList.remove("visible"); }; const close = () => { container.style.opacity = "0"; if (loginBox) { loginBox.style.transform = "scale(0.9)"; } setTimeout(() => { if (document.body.contains(container)) { document.body.removeChild(container); } }, 500); }; container.querySelector(".close-button-v8").onclick = close; container.querySelector(".login-button-v8").onclick = async () => { const username = container.querySelector('input[type="text"]').value; const password = container.querySelector('input[type="password"]').value; if (!username || !password) { showError("请输入用户名和密码!"); return; } try { const response = await fetch(baseUrl + "/business/member/login", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ username, password }), }); const data = await response.json(); if (data.code == 200 && data.data.access_token) { GM_setValue("myToken", data.data.access_token); // localStorage.setItem("token", data.accessToken); fetchUserInfo(); container.remove(); } else { throw new Error(data.msg || "登录失败"); } } catch (error) { showError("用户名或密码错误。"); } }; // 新增: 注册链接点击事件 container.querySelector(".register-link-v8").onclick = (e) => { e.preventDefault(); window.location.href = "https://www.haijiaom.cc"; // 在这里可以添加跳转到注册页或显示注册模态框的逻辑 }; } function createPersonalInfoDialog() { if (!checkToken()) { return; } let featureEnabled = GM_getValue("feature_enabled", true); let memberLvel = vipUser.sites.filter((item) => { return item.siteCode === siteCode; }); if (memberLvel[0]) { featureEnabled = memberLvel[0].scriptOpen; } const dialog = document.createElement("div"); dialog.innerHTML = ` <style> .info-dialog { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: white; padding: 20px; border-radius: 12px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); width: 90%; max-width: 320px; box-sizing: border-box; z-index: 10000; } .info-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; } .info-close { width: 24px; height: 24px; border: none; border-radius: 50%; background: #f5f5f5; color: #666; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: all 0.3s; } .info-avatar { width: 60px; height: 60px; border-radius: 50%; background: #4a90e2; color: white; display: flex; align-items: center; justify-content: center; margin: 0 auto 15px; } .info-stats { display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px; margin: 15px 0; } .info-stat-item { background: #f8f9fa; padding: 10px; border-radius: 8px; text-align: center; } .info-actions { display: grid; gap: 10px; margin-top: 15px; } .vip-btn { background: #4a90e2; color: white; padding: 10px; border-radius: 8px; text-decoration: none; text-align: center; font-size: 14px; } .logout-btn { background: #f44336; color: white; border: none; padding: 10px; border-radius: 8px; cursor: pointer; font-size: 14px; } .expired-notice { background: #fff3cd; color: #856404; padding: 10px; border-radius: 8px; margin: 10px 0; font-size: 13px; } .script-settings { border-top: 1px solid #f0f0f0; margin-top: 15px; padding-top: 10px; } .setting-item { display: flex; justify-content: space-between; align-items: center; padding: 8px 0; } .setting-item span { font-size: 14px; color: #333; } .switch { position: relative; display: inline-block; width: 44px; height: 24px; } .switch input { opacity: 0; width: 0; height: 0; } .slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; transition: .4s; border-radius: 24px; } .slider:before { position: absolute; content: ""; height: 20px; width: 20px; left: 2px; bottom: 2px; background-color: white; transition: .4s; border-radius: 50%; } input:checked + .slider { background-color: #4a90e2; } input:checked + .slider:before { transform: translateX(20px); } </style> <div class="info-dialog"> <div class="info-header"> <h3 style="margin:0">个人信息</h3> <button class="info-close">×</button> </div> <div class="info-avatar"> <i class="fas fa-user fa-2x"></i> </div> <div style="text-align:center"> <h2 style="margin:0;font-size:18px">${ vipUser.username || "未登录" }</h2> <p style="margin:5px 0;color:#666;font-size:13px"> ${vipUser.vipLevel || "普通用户"} </p> </div> <div class="info-stats"> <div class="info-stat-item"> <div style="font-size:18px;color:#4a90e2;font-weight:bold"> ${vipUser.downloadNum || 0} </div> <div style="font-size:12px;color:#666">下载次数</div> </div> <div class="info-stat-item"> <div style="font-size:18px;color:#4a90e2;font-weight:bold"> ${vipUser.watchNum || 0} </div> <div style="font-size:12px;color:#666">观看次数</div> </div> </div> ${ vipUser.expireTime ? ` <div class="expired-notice"> ${ new Date(vipUser.expireTime) > new Date() ? `会员有效期至:${new Date( vipUser.expireTime ).toLocaleDateString()}` : "您的会员已过期,请重新开通" } </div> ` : "" } <div class="script-settings"> <div class="setting-item"> <span>脚本开关</span> <label class="switch"> <input type="checkbox" id="featureToggle" ${ featureEnabled ? "checked" : "" }> <span class="slider"></span> </label> </div> </div> <div class="info-actions"> <a href="https://www.haijiaom.cc/" class="vip-btn"> <i class="fas fa-crown"></i> ${ vipUser.expireTime && new Date(vipUser.expireTime) > new Date() ? "续费会员" : "开通会员" } </a> <button class="logout-btn" id="logoutBtn"> <i class="fas fa-sign-out-alt"></i> 退出登录 </button> </div> </div> `; document.body.appendChild(dialog); const featureToggle = dialog.querySelector("#featureToggle"); if (featureToggle) { featureToggle.addEventListener("change", async () => { const isEnabled = featureToggle.checked; featureToggle.disabled = true; try { const result = await putAsync( baseUrl + "/business/member/script-operation", { scriptOpen: isEnabled, siteCode: siteCode, } ); if (result.code == 200) { GM_setValue("feature_enabled", isEnabled); showApiMessage( `功能已${isEnabled ? "开启" : "关闭"},页面即将刷新...` ); setTimeout(() => { window.location.reload(); }, 1500); } else { showApiMessage(result.msg || "操作失败", "error"); featureToggle.checked = !isEnabled; } } catch (error) { showApiMessage("请求失败,请检查网络连接", "error"); featureToggle.checked = !isEnabled; } finally { featureToggle.disabled = false; } }); } // Add logout handler dialog.querySelector("#logoutBtn").addEventListener("click", () => { GM_setValue("myToken", ""); dialog.remove(); setTimeout(() => window.location.reload(), 1000); }); // Add close handler dialog.querySelector(".info-close").addEventListener("click", () => { dialog.remove(); }); } const baseTools = [ { name: "在线下载1", icon: "fas fa-bolt", url: "https://tools.thatwind.com/tool/m3u8downloader", clients: ["windows", "mac"], }, { name: "在线下载2", icon: "fas fa-cloud-download-alt", url: "http://tools.bugscaner.com/m3u8.html", clients: ["windows", "mac", "linux"], }, ]; // 根据客户端类型获取工具列表 function getDownloadTools() { const client = detectClient(); const mobileTools = [ { name: "安卓视频下载软件", icon: "fas fa-mobile-alt", url: "market://details?id=com.xunlei.downloadprovider", clients: ["android"], }, { name: "苹果视频下载软件", icon: "fas fa-file-download", url: "https://apps.apple.com/cn/app/m3u8-mpjex/id6449724938", clients: ["ios"], }, ]; const tools = [...baseTools, ...mobileTools].filter((tool) => tool.clients.includes(client) ); return tools; } // 检测客户端类型 function detectClient() { const ua = navigator.userAgent.toLowerCase(); if (/android/i.test(ua)) return "android"; if (/iphone|ipad|ipod/i.test(ua)) return "ios"; if (/macintosh/i.test(ua)) return "mac"; if (/linux/i.test(ua)) return "linux"; return "windows"; } function createDownloadDialog(downloadUrl) { if (!checkToken()) { return; } const firstDialog = document.createElement("div"); firstDialog.style.cssText = ` position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: white; padding: 25px; padding-top: 40px; border-radius: 12px; box-shadow: 0 10px 25px rgba(0,0,0,0.15); z-index: 10000; min-width: 320px; text-align: center; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; animation: fadeIn 0.3s ease; `; const styleSheet = document.createElement("style"); styleSheet.textContent = ` @keyframes fadeIn { from { opacity: 0; transform: translate(-50%, -48%); } to { opacity: 1; transform: translate(-50%, -50%); } } .dialog-btn { padding: 10px 24px; margin: 0 8px; border: none; border-radius: 6px; font-size: 14px; font-weight: 500; cursor: pointer; transition: all 0.2s ease; } .dialog-btn:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0,0,0,0.1); } .primary-btn { background: linear-gradient(135deg, #4a90e2, #357abd); color: white; } .secondary-btn { background: #f5f5f5; color: #666; } .dialog-close { position: absolute; top: 10px; right: 10px; width: 24px; height: 24px; border-radius: 50%; background: #f5f5f5; border: none; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: all 0.2s ease; color: #666; font-size: 14px; } .dialog-close:hover { background: #e0e0e0; transform: rotate(90deg); } `; document.head.appendChild(styleSheet); firstDialog.innerHTML = ` <button class="dialog-close">✕</button> <h3 style="margin: 0 0 20px; font-size: 20px; color: #333; font-weight: 500;">下载确认</h3> <p style="margin: 0 0 8px; color: #666; font-size: 15px;">本次下载将消耗1次下载机会</p> <p style="margin: 0 0 25px; font-size: 15px;"> 剩余下载次数:<span style="color: #ff4444; font-weight: 600;">${vipUser.downloadNum}</span> </p> <div style="margin-top: 25px;"> <button id="confirmDownload" class="dialog-btn primary-btn">确认下载</button> <button id="cancelDownload" class="dialog-btn secondary-btn">取消</button> </div> `; document.body.appendChild(firstDialog); function showDownloadOptions() { const secondDialog = document.createElement("div"); secondDialog.style.cssText = firstDialog.style.cssText; const downloadTools = getDownloadTools(); const toolButtons = downloadTools .map( (tool) => ` <button id="${tool.name}" class="dialog-btn primary-btn" style=" width: 100%; margin: 8px 0; background: linear-gradient(135deg, #4CAF50, #45a049); display: flex; align-items: center; justify-content: center; gap: 8px; "> <i class="${tool.icon}"></i>${tool.name} </button> ` ) .join(""); secondDialog.innerHTML = ` <button class="dialog-close">✕</button> <h3 style="margin: 0 0 20px; font-size: 20px; color: #333; font-weight: 500;">下载选项</h3> <div style="margin: 20px 0;"> <button id="copyLink" class="dialog-btn primary-btn" style=" width: 100%; margin: 8px 0; display: flex; align-items: center; justify-content: center; gap: 8px; "> <i class="fas fa-copy"></i>复制下载链接 </button> ${toolButtons} </div> `; document.body.appendChild(secondDialog); // 为每个工具添加点击事件 downloadTools.forEach((tool) => { document.getElementById(tool.name)?.addEventListener("click", () => { window.open(tool.url, "_blank"); }); }); // 复制链接事件 document.getElementById("copyLink").addEventListener("click", () => { navigator.clipboard.writeText(slefVideo.downloadUrl); showApiMessage("下载链接已复制到剪贴板"); }); // 关闭按钮事件 secondDialog .querySelector(".dialog-close") .addEventListener("click", () => { secondDialog.remove(); }); } // 确认下载事件 document .getElementById("confirmDownload") .addEventListener("click", async () => { firstDialog.remove(); let result = await gcreateDownloadUrl(); if (result.code == 200) { slefVideo.downloadUrl = baseDownadloadUrl + result.data; if (vipUser.downloadNum == 0) { showApiMessage("下载次数已用完", "error"); return; } showDownloadOptions(); } }); // 取消下载事件 document.getElementById("cancelDownload").addEventListener("click", () => { firstDialog.remove(); }); // 关闭按钮事件 firstDialog.querySelector(".dialog-close").addEventListener("click", () => { firstDialog.remove(); }); } function initHlsPlayer() { const hlsScript = document.createElement("script"); hlsScript.src = "https://cdnjs.cloudflare.com/ajax/libs/hls.js/1.5.8/hls.min.js"; document.head.appendChild(hlsScript); const playerContainer = document.createElement("div"); playerContainer.id = "hls-player-container"; playerContainer.style.cssText = ` position: fixed; top: 0; bottom: 0; left:0; right:0; background: rgba(0, 0, 0, 1); padding: 10px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.2); z-index: 9996; display: none; `; // Add close button for player const closeButton = document.createElement("button"); closeButton.innerHTML = '<i class="fas fa-times"></i>'; closeButton.style.cssText = ` position: absolute; top: 10px; right: 10px; background: rgba(0,0,0,0.5); color: white; border: none; border-radius: 50%; width: 36px; height: 36px; font-size:16px; cursor: pointer; z-index: 9999; display: flex; align-items: center; justify-content: center; `; closeButton.onclick = () => { window.hlsPlayer.hide(); }; playerContainer.appendChild(closeButton); let currentUrl = ""; // Create video element const video = document.createElement("video"); video.id = "hls-video"; video.style.cssText = ` width: 100%; height: 100%; object-fit: contain; `; video.controls = true; playerContainer.appendChild(video); document.body.appendChild(playerContainer); hlsScript.onload = () => { window.hlsPlayer = { setContent: (input) => { if (isM3U8Content(input)) { // Handle M3U8 content string currentUrl = createBlobUrl(input); } else { // Handle URL currentUrl = input; } }, play: () => { if (!currentUrl) { console.error("Please set URL first"); return; } playerContainer.style.display = "block"; if (Hls.isSupported()) { const hls = new Hls(); hls.loadSource(currentUrl); hls.attachMedia(video); hls.on(Hls.Events.MANIFEST_PARSED, () => { video.play(); isPlaying = true; updatePlayButton(); }); } }, pause: () => { video.pause(); isPlaying = false; updatePlayButton(); }, toggle: () => { if (isPlaying) { window.hlsPlayer.pause(); } else { window.hlsPlayer.play(); } }, hide: () => { playerContainer.style.display = "none"; video.pause(); isPlaying = false; updatePlayButton(); toggleVisibility(); }, }; }; } function isM3U8Content(str) { return str.trim().startsWith("#EXTM3U"); } function updatePlayButton() { const playBtn = document.getElementById("playBtn"); if (playBtn) { playBtn.innerHTML = isPlaying ? '<i class="fas fa-pause"></i>' : '<i class="fas fa-play"></i>'; playBtn.style.background = isPlaying ? "#4CAF50" : "#4a90e2"; } } async function togglePlay() { if (!(await checkToken())) { return; } if (!(await checkPermission())) { return; } if (slefVideo.videoPlayUrl == "") { showApiMessage( "当前页面没有视频,或者当前视频无需VIP权限,请在原有网站观看", "error" ); return; } let params = Object.assign( {}, { remoteUrl: slefVideo.videoPlayUrl, videoArticleId: slefVideo.articleId, videoDuration: slefVideo.videoDuration, videoId: slefVideo.id, siteCode: siteCode, } ); let result = await postAsync(baseUrl + "/business/video/serialize", params); slefVideo.iv = result.data.iv; slefVideo.videoBaseUrl = result.data.videoBaseUrl; slefVideo.uri = result.data.videoKeyUri; slefVideo.tsFileName = result.data.videoTsName; let temp = generateM3U8FromVideo(slefVideo, slefVideo.videoDuration); window.hlsPlayer.setContent(temp); window.hlsPlayer.play(); toggleVisibility(); } function generateM3U8FromVideo(slefVideo, duration) { // Generate segment duration between 1.0 and 1.25 seconds let segmentDuration = (Math.random() * 0.25 + 1.0).toFixed(6); const totalSegments = Math.ceil(duration / segmentDuration); let m3u8Content = "#EXTM3U" + "\r\n"; m3u8Content += "#EXT-X-VERSION:3" + "\r\n"; m3u8Content += "#EXT-X-TARGETDURATION:11" + "\r\n"; m3u8Content += "#EXT-X-MEDIA-SEQUENCE:0" + "\r\n"; // 添加加密信息 if (slefVideo.uri) { m3u8Content += `#EXT-X-KEY:METHOD=AES-128,URI="${slefVideo.videoBaseUrl}/${slefVideo.uri}"`; if (slefVideo.iv) { m3u8Content += `,IV=0x${slefVideo.iv}`; } m3u8Content += "\n"; } // 生成视频片段 for (let i = 0; i < totalSegments; i++) { const remainingDuration = duration - i * segmentDuration; const currentSegmentDuration = Math.min( segmentDuration, remainingDuration ); m3u8Content += `#EXTINF:${currentSegmentDuration},\n`; m3u8Content += `${slefVideo.videoBaseUrl}/${slefVideo.tsFileName}${i}.ts\n`; } m3u8Content += "#EXT-X-ENDLIST"; return m3u8Content; } function createBlobUrl(m3u8Content) { const blob = new Blob([m3u8Content], { type: "application/x-mpegURL" }); return URL.createObjectURL(blob); } function serializeM3u8() { let m3u8Content = ""; fetch(m3u8Url, { method: "GET", }) .then((res) => res.text()) .then((data) => { m3u8Content = data; let lines = m3u8Content.split("\n"); slefVideo.m3u8Url = lines[lines.length - 2]; }); } function toggleVisibility() { const navbar = document.getElementById("custom-navbar"); const floatingBtn = document.getElementById("floating-toggle-btn"); const isVisible = navbar.style.display !== "none"; navbar.style.display = isVisible ? "none" : "block"; floatingBtn.style.display = isVisible ? "flex" : "none"; } const OriginalXHR = unsafeWindow.XMLHttpRequest || window.XMLHttpRequest; function CustomXHR() { const xhr = new OriginalXHR(); const originalOpen = xhr.open; const originalSend = xhr.send; let requestURL = ""; let modifiedRequestURL = ""; const urlMappings = { "videos/v2/getUrl": (url) => { // Example: Change URL for video requests return url.replace( url, "https://api.qianyuewenhua.xyz/videos/getPreUrl" ); }, }; function modifyURL(originalURL) { for (const [pattern, modifier] of Object.entries(urlMappings)) { if (originalURL.includes(pattern)) { modifiedRequestURL = modifier(originalURL); return modifiedRequestURL; } } return originalURL; } // Add URL modifications before open() is called xhr.open = function () { requestURL = arguments[1]; let modifiedURL = modifyURL(requestURL); arguments[1] = modifiedURL; return originalOpen.apply(this, arguments); }; // xhr.open = function () { // requestURL = arguments[1]; // return originalOpen.apply(this, arguments); // }; xhr.send = function () { const originalOnReadyStateChange = xhr.onreadystatechange; xhr.onreadystatechange = function () { if (xhr.readyState === 4) { if (xhr.status === 200) { try { // URL pattern matching Object.keys(urlPatterns).forEach((pattern) => { if (new RegExp(pattern).test(requestURL)) { let response = JSON.parse(xhr.responseText); const modifiedResponse = urlPatterns[pattern](response); if (modifiedResponse) { Object.defineProperty(xhr, "response", { value: JSON.stringify(modifiedResponse), }); Object.defineProperty(xhr, "responseText", { value: JSON.stringify(modifiedResponse), }); } console.log( "[Modified Response]:", requestURL, modifiedResponse ); } }); } catch (error) { console.error("[XHR Error]:", error, requestURL); } } } if (originalOnReadyStateChange) { originalOnReadyStateChange.apply(this, arguments); } }; return originalSend.apply(this, arguments); }; return xhr; } if (GM_getValue("feature_enabled", true)) { // Replace XMLHttpRequest if (typeof unsafeWindow !== "undefined") { unsafeWindow.XMLHttpRequest = CustomXHR; } else { window.XMLHttpRequest = CustomXHR; } } const urlPatterns = { "api\\/video\\/checkVideoCanPlay(\\?[^/]*)?$": (response) => { let decodeData = response.data; let jsonStr = decodeURIComponent(escape(atob(atob(atob(response.data))))); let objectData = JSON.parse(jsonStr, `utf-8`); const freeVideo = { type: 1, amount: 0, money_type: 0, vip: 0 }; response.data = Object.assign({}, objectData, freeVideo); return response; }, "api\\/banner\\/banner_list(\\?[^/]*)?$": (response) => { return null; }, "videos\\/getInfo(\\?[^/]*)?$": (response) => { if (checkToken() && hasPrev) { response.data.canPlay = true; slefVideo.id = response.data.info.id; } return response; }, "videos\\/v2\\/getUrl(\\?[^/]*)?$": (response) => { if (checkToken() && hasPrev) { response.data.url = response.data.url.replace( /start=\d+\&end=\d+\&/, "" ); m3u8Url = response.data.url; serializeM3u8(); } return response; }, "api\\/favorite\\/v2\\/folderList(\\?[^/]*)?$": (response) => { return null; }, "api\\/topic\\/\\d+": (response) => { let decodeData = response.data; analysisVideo(decodeData); return response; }, "api\\/topic\\/hot\\/topics(\\?.*)?$": (response) => { let decodeData = response.data; let refStr = analysisTitle(decodeData); response.data = btoaUnicode(refStr); return response; }, "api\\/login\\/signin": (response) => { if (response.success) { let username = $(".login-form input").first().val(); let password = $(".login-form input").eq(1).val(); GM_setValue(hjsqUserKye, JSON.stringify({ username, password })); } return response; }, }; function btoaUnicode(str) { return btoa( btoa( btoa( encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, (match, p1) => { return String.fromCharCode(parseInt(p1, 16)); }) ) ) ); } // 你想要填充的用户名和密码 let username = ""; let password = ""; // 目标选择器 const formSelector = ".login-form"; // 根据实际情况修改 const inputSelector = "input"; // 当找到输入框时进行填充 function fillInputsIfAvailable(mutationsList, observer) { // 检查是否找到了目标表单 const form = document.querySelector(formSelector); if (form) { // 获取所有的 input 元素 let user = JSON.parse(GM_getValue(hjsqUserKye)); if (user) { username = user.username || username; password = user.password || password; } console.log(user); console.log(user.username); const inputs = form.querySelectorAll(inputSelector); if (inputs.length >= 2) { // 停止观察,因为我们已经找到了需要的元素 observer.disconnect(); // 填充用户名和密码 inputs[0].value = username; inputs[1].value = password; // 触发事件,确保页面上的任何事件监听器都能捕获到值的变化 ["input", "change"].forEach((eventType) => { inputs[0].dispatchEvent(new Event(eventType)); inputs[1].dispatchEvent(new Event(eventType)); }); } } } // 创建一个 MutationObserver 实例,传递回调函数 const observer = new MutationObserver(fillInputsIfAvailable); // 配置观察选项: const config = { attributes: false, childList: true, subtree: true }; // 选择需要观察的节点(在这里是整个 body,但你可以根据情况调整) const targetNode = document.body; if (GM_getValue("feature_enabled", true)) { // 开始观察目标节点 observer.observe(targetNode, config); // 初始检查一次,以防元素已经存在 fillInputsIfAvailable([], observer); } async function analysisVideo(content) { let jsonStr = decodeURIComponent(escape(atob(atob(atob(content))))); let objectData = JSON.parse(jsonStr, `utf-8`); slefVideo.articleId = objectData.topicId; objectData.attachments.forEach((item) => { if (item.category == "video" && item.remoteUr != "") { slefVideo.videoPlayUrl = item.remoteUrl; slefVideo.m3u8Url = item.remoteUrl; slefVideo.videoDuration = item.video_time_length; slefVideo.id = item.id; // Extract URI from preview URL } // if(item.category == "video" && item.remoteUr ==""){ // console.log("普通视频!") //} }); } function analysisTitle(content) { let jsonStr = decodeURIComponent(escape(atob(atob(atob(content))))); let objectData = JSON.parse(jsonStr, `utf-8`); objectData.results.forEach((item) => { if (item.attachments) { let dataId = ""; item.attachments.forEach((e) => { if (item.hasVideo && dataId) { sDataId = dataId; markTitle(); } dataId = e.id; }); } }); return JSON.stringify(objectData); } function showApiMessage(message = "操作成功", type = "success") { // 防止重复弹窗 if (document.getElementById("api-message-dialog")) return; const colorMap = { success: "#43a047", error: "#d32f2f", info: "#1976d2", warning: "#ffa000", }; const iconMap = { success: "fa-check-circle", error: "fa-exclamation-triangle", info: "fa-info-circle", warning: "fa-exclamation-circle", }; const dialog = document.createElement("div"); dialog.id = "api-message-dialog"; dialog.innerHTML = ` <style> .api-message-dialog { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: #fff; color: ${colorMap[type] || "#333"}; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.15); padding: 24px 20px 16px 20px; min-width: 220px; z-index: 10001; text-align: center; font-size: 15px; } .api-message-close { margin-top: 18px; background: ${colorMap[type] || "#333"}; color: #fff; border: none; border-radius: 6px; padding: 8px 24px; font-size: 14px; cursor: pointer; } </style> <div class="api-message-dialog"> <div style="font-size:18px;margin-bottom:10px;"> <i class="fas ${iconMap[type] || "fa-info-circle"}"></i> </div> <div>${message}</div> <button class="api-message-close">关闭</button> </div> `; document.body.appendChild(dialog); dialog.querySelector(".api-message-close").onclick = () => dialog.remove(); setTimeout(() => dialog.remove(), 4500); } function waitForPageLoad() { return new Promise((resolve, reject) => { // Set timeout for safety const timeout = setTimeout(() => { reject("Page load timeout"); }, 10000); // Check if page is already loaded if (document.readyState === "complete") { clearTimeout(timeout); resolve(document.body); return; } // Create observer to monitor DOM changes const observer = new MutationObserver((mutations, obs) => { if (document.readyState === "complete" && document.body) { clearTimeout(timeout); obs.disconnect(); resolve(document.body); } }); // Start observing observer.observe(document.documentElement, { childList: true, subtree: true, }); }); } function checkToken() { const token = GM_getValue("myToken", ""); if (!token) { createLoginForm(); return false; } return true; } async function fetchUserInfo() { try { const response = await fetch( baseUrl + "/business/member/info?siteCode=" + siteCode, { method: "GET", headers: { Authorization: `Bearer ${GM_getValue("myToken", "")}`, }, } ); const data = await response.json(); vipUser = data.data; if (data.code == 401) { createLoginForm(); // createLoginView(); } else { checkPermission(); } return data.data; } catch (error) { console.error("Error fetching user info:", error); throw error; } } async function postAsync(url, body) { try { const response = await fetch(url, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${GM_getValue("myToken", "")}`, // Add token from localStorage }, body: JSON.stringify(body), }); const result = await response.json(); if (!response.ok) { throw new Error(`HTTP 错误!状态码:${response.status}`); } if (result.code != 200) { showApiMessage(result.msg, "error"); } if (result.code == 401) { GM_setValue("myToken", ""); createLoginForm(); } return result; } catch (error) { showApiMessage("操作失败", "error"); throw error; } } async function putAsync(url, body) { try { const response = await fetch(url, { method: "PUT", headers: { "Content-Type": "application/json", Authorization: `Bearer ${GM_getValue("myToken", "")}`, }, body: JSON.stringify(body), }); const result = await response.json(); if (!response.ok) { throw new Error(`HTTP 错误!状态码:${response.status}`); } if (result.code != 200) { showApiMessage(result.msg, "error"); } if (result.code == 401) { GM_setValue("myToken", ""); createLoginForm(); } return result; } catch (error) { showApiMessage("操作失败", "error"); throw error; } } async function getAsync(url, params) { try { // Build query string from params if they exist const queryString = params ? "?" + new URLSearchParams(params).toString() : ""; const response = await fetch(baseUrl + url + queryString, { method: "GET", headers: { Authorization: `Bearer ${GM_getValue("myToken", "")}`, }, }); const result = await response.json(); if (!response.ok) { throw new Error(`HTTP 错误!状态码:${response.status}`); } if (result.code != 200 && result.code != 401) { showApiMessage(result.msg, "error"); } if (result.code == 401) { GM_setValue("myToken", ""); createLoginForm(); } return result; } catch (error) { showApiMessage("操作失败", "error"); throw error; } } async function checkPermission() { var result = await getAsync("/business/member/has-permission", { siteCode: siteCode, }); if (result.code == 200) { hasPrev = true; return true; } else { return false; } } async function serializeVideo() { let params = Object.assign( {}, { videoId: slefVideo.id, m3u8Url: slefVideo.m3u8Url, siteCode: siteCode, } ); let result = await postAsync(baseUrl + "/business/video/serialize", params); } async function gcreateDownloadUrl() { if (slefVideo.m3u8Url) { let params = Object.assign( {}, { videoId: slefVideo.id, m3u8Url: slefVideo.m3u8Url, siteCode: siteCode, } ); return await postAsync( baseUrl + "/business/download/generate-token", params ); } else { showApiMessage("请等待视频加载完成"); } } function markTitle() { waitForElement("img[data-id='" + sDataId + "']").then((element) => { if (element) { // 2. 获取目标元素的父元素 const parent = element.parentElement; // 3. 获取父元素的兄弟元素(即 parent 的父元素下的所有子元素) const parentSiblings = Array.from(parent.parentElement.children); // 4. 过滤出 class 包含 "title" 的兄弟元素 const titleElements = parentSiblings.filter((sibling) => { // 排除自己(parent) return sibling !== parent && sibling.classList.contains("t-title"); }); const titlePcElements = parentSiblings.filter((sibling) => { // 排除自己(parent) return sibling !== parent && sibling.tagName === "H4"; }); titleElements.forEach((element) => { element.style.color = "red"; }); titlePcElements.forEach((h4Element) => { const childElements = h4Element.children; for (let child of childElements) { child.style.color = "red"; } }); // 5. 输出结果 } }); } function waitForElement(selector) { return new Promise((resolve) => { if (document.querySelector(selector)) { return resolve(document.querySelector(selector)); } const observer = new MutationObserver((mutations, obs) => { if (document.querySelector(selector)) { obs.disconnect(); resolve(document.querySelector(selector)); } }); observer.observe(document.body, { childList: true, subtree: true, }); }); } function removeAdds() { GM_addStyle( //海角 ".containeradvertising { display: none; } .my-swipe{ display: none;} .bannerliststyle{display: none;} .topbanmer{display: none;} .btnbox{display: none;} .addbox{display: none;} .van-swipe__track{display: none;} .html-bottom-box{display: none;} #bbs_float_menu{display: none;} #tidio-chat{display: none;} .ishide {max-height: unset !important; overflow: visible !important;position: relative;}" ); waitForElement(".my-swipe").then((e) => { e.style.display = "none"; }); waitForElement(".addbox").then((e) => { e.style.display = "none"; }); } })();