// ==UserScript== // @name loader // @namespace liang // @version 0.2.2 // @description 仅负责按 stable.json 拉取远程稳定版脚本 // @match https://osmg.yswg.com.cn/* // @run-at document-end // @grant GM_getValue // @grant GM_setValue // @grant GM_xmlhttpRequest // @grant GM_registerMenuCommand // @grant GM_addStyle // @grant GM_notification // @grant GM_openInTab // @connect liang.website // @downloadURL https://liang.website/tm/loader.user.js // @updateURL https://liang.website/tm/loader.user.js // ==/UserScript== (function () { 'use strict'; const BASE_URL = 'https://liang.website'; const STABLE_META_URL = BASE_URL.replace(/\/$/, '') + '/tm/stable.json?t=' + Date.now(); const FALLBACK_FULL_URL = BASE_URL.replace(/\/$/, '') + '/tm/full.js?t=' + Date.now(); const _GM_registerMenuCommand = (typeof GM_registerMenuCommand === 'function' && GM_registerMenuCommand) || (typeof GM === 'object' && GM && typeof GM.registerMenuCommand === 'function' ? GM.registerMenuCommand.bind(GM) : undefined); const _GM_addStyle = (typeof GM_addStyle === 'function' && GM_addStyle) || (typeof GM === 'object' && GM && typeof GM.addStyle === 'function' ? GM.addStyle.bind(GM) : undefined); const _GM_notification = (typeof GM_notification === 'function' && GM_notification) || (typeof GM === 'object' && GM && typeof GM.notification === 'function' ? GM.notification.bind(GM) : undefined); const _GM_openInTab = (typeof GM_openInTab === 'function' && GM_openInTab) || (typeof GM === 'object' && GM && typeof GM.openInTab === 'function' ? GM.openInTab.bind(GM) : undefined); const isFirstLoad = GM_getValue('liang_loader_first_load', true); function initBubbleStyle() { const style = ` .liang-bubble-notice { position: fixed; top: 20px; right: 20px; padding: 12px 20px; border-radius: 8px; color: white; font-size: 14px; z-index: 999999; box-shadow: 0 2px 10px rgba(0,0,0,0.2); transition: all 0.3s ease; opacity: 0; transform: translateX(100%); } .liang-bubble-notice.show { opacity: 1; transform: translateX(0); } .liang-bubble-notice.info { background-color: #4CAF50; } .liang-bubble-notice.error { background-color: #f44336; } `; if (_GM_addStyle) { _GM_addStyle(style); } else { const styleEl = document.createElement('style'); styleEl.textContent = style; document.head.appendChild(styleEl); } } function showBubbleNotify(text, type) { const noticeType = type || 'info'; try { let bubble = document.querySelector('.liang-bubble-notice'); if (!bubble) { bubble = document.createElement('div'); bubble.className = 'liang-bubble-notice'; document.body.appendChild(bubble); } bubble.textContent = text; bubble.className = 'liang-bubble-notice ' + noticeType; bubble.classList.add('show'); setTimeout(function () { bubble.classList.remove('show'); setTimeout(function () { if (bubble && bubble.parentNode) bubble.remove(); }, 300); }, 2500); } catch (e) { console.log('[Liang Loader] 气泡通知创建失败:', e); console.log('[Liang Loader]', text); } } function notify(text, type) { if (text === '脚本已加载 ✅' && !isFirstLoad) { console.log('[Liang Loader]', text); return; } showBubbleNotify(text, type || 'info'); console.log('[Liang Loader]', text); } function runRemoteScript(scriptText, sourceUrl) { const runner = new Function( 'GM_getValue', 'GM_setValue', 'GM_xmlhttpRequest', 'GM_registerMenuCommand', 'GM_addStyle', 'GM_notification', 'GM_openInTab', 'unsafeWindow', String(scriptText || '') + '\n//# sourceURL=' + String(sourceUrl || 'liang_full.js') ); runner( GM_getValue, GM_setValue, GM_xmlhttpRequest, _GM_registerMenuCommand, _GM_addStyle, _GM_notification, _GM_openInTab, typeof unsafeWindow !== 'undefined' ? unsafeWindow : window ); if (isFirstLoad) { GM_setValue('liang_loader_first_load', false); } notify('脚本已加载 ✅'); } function fetchAndRunScript(scriptUrl, versionTag) { GM_xmlhttpRequest({ method: 'GET', url: scriptUrl, timeout: 20000, onload: function (res) { if (res.status !== 200) { console.warn('[Liang Loader] load remote script failed:', res.status, scriptUrl); notify('加载脚本失败:' + res.status, 'error'); return; } try { if (versionTag) GM_setValue('liang_loader_last_version', String(versionTag)); GM_setValue('liang_loader_last_url', String(scriptUrl)); runRemoteScript(res.responseText, scriptUrl); } catch (e) { console.error('[Liang Loader] run remote script error:', e); notify('执行脚本失败(F12 控制台查看)', 'error'); } }, onerror: function (e) { console.error('[Liang Loader] request remote script error:', e); notify('请求脚本失败(网络错误)', 'error'); }, ontimeout: function () { console.error('[Liang Loader] request remote script timeout'); notify('请求脚本超时', 'error'); } }); } function fetchStableMeta() { GM_xmlhttpRequest({ method: 'GET', url: STABLE_META_URL, timeout: 12000, onload: function (res) { if (res.status !== 200) { console.warn('[Liang Loader] stable.json not available, fallback full.js:', res.status); fetchAndRunScript(FALLBACK_FULL_URL, 'fallback-full.js'); return; } try { const meta = JSON.parse(res.responseText || '{}'); const version = String(meta.version || '').trim(); const rawScriptUrl = String(meta.url || '').trim(); if (!rawScriptUrl) { console.warn('[Liang Loader] stable.json missing url, fallback full.js'); fetchAndRunScript(FALLBACK_FULL_URL, version || 'fallback-full.js'); return; } const resolvedScriptUrl = /^https?:\/\//i.test(rawScriptUrl) ? rawScriptUrl : (BASE_URL.replace(/\/$/, '') + '/' + rawScriptUrl.replace(/^\//, '')); const scriptUrl = resolvedScriptUrl + (resolvedScriptUrl.includes('?') ? '&' : '?') + 't=' + Date.now(); fetchAndRunScript(scriptUrl, version || rawScriptUrl); } catch (e) { console.error('[Liang Loader] parse stable.json error:', e); fetchAndRunScript(FALLBACK_FULL_URL, 'fallback-full.js'); } }, onerror: function (e) { console.error('[Liang Loader] request stable.json error:', e); fetchAndRunScript(FALLBACK_FULL_URL, 'fallback-full.js'); }, ontimeout: function () { console.error('[Liang Loader] request stable.json timeout'); fetchAndRunScript(FALLBACK_FULL_URL, 'fallback-full.js'); } }); } initBubbleStyle(); fetchStableMeta(); })();