// ==UserScript==
// @name 网站保活助手
// @namespace http://tampermonkey.net/
// @version 1.0
// @description 在指定网站上自动保活,定时滚动和刷新页面,用户操作会重置计时
// @author damu
// @match *://*/*
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_registerMenuCommand
// @grant GM_notification
// @run-at document-idle
// @license MIT
// ==/UserScript==
(function() {
'use strict';
const DEFAULT_SCROLL = 5, DEFAULT_REFRESH = 20, DEFAULT_ACTIVITY = 1;
let scrollTimer, refreshTimer, lastActivityTime = Date.now();
let globalEnabled = GM_getValue('globalEnabled', true);
let sites = GM_getValue('keepAliveSites', []);
// 修复旧数据格式:如果sites是字符串数组,转换为对象数组
if (sites.length > 0 && typeof sites[0] === 'string') {
sites = sites.map(url => ({
url: url,
enabled: true,
scroll: DEFAULT_SCROLL,
refresh: DEFAULT_REFRESH,
activity: DEFAULT_ACTIVITY
}));
GM_setValue('keepAliveSites', sites);
}
GM_registerMenuCommand("网站保活管理", openManagerUI);
GM_registerMenuCommand("添加当前网站", addCurrentSite);
function addCurrentSite() {
const currentUrl = window.location.origin + window.location.pathname;
const sitePattern = currentUrl + (window.location.pathname.endsWith('/') ? '*' : '/*');
if (!sites.some(s => s.url === sitePattern)) {
sites.push({url: sitePattern, enabled: true, scroll: DEFAULT_SCROLL, refresh: DEFAULT_REFRESH, activity: DEFAULT_ACTIVITY});
GM_setValue('keepAliveSites', sites);
GM_notification({ text: `已添加 ${sitePattern}`, timeout: 2000 });
if (globalEnabled) initKeepAlive();
} else {
GM_notification({ text: '网站已存在', timeout: 2000 });
}
}
function openManagerUI() {
if (!document.getElementById('keepAliveManager')) createManagerUI();
document.getElementById('keepAliveOverlay').style.display = 'block';
document.getElementById('keepAliveManager').style.display = 'block';
}
function closeManagerUI() {
document.getElementById('keepAliveOverlay').style.display = 'none';
document.getElementById('keepAliveManager').style.display = 'none';
}
function addSiteFromUI() {
const url = document.getElementById('siteUrl').value.trim();
if (url && !sites.some(s => s.url === url)) {
sites.push({url: url, enabled: true, scroll: DEFAULT_SCROLL, refresh: DEFAULT_REFRESH, activity: DEFAULT_ACTIVITY});
renderSiteList();
document.getElementById('siteUrl').value = '';
}
}
function saveConfig() {
globalEnabled = document.getElementById('globalToggle').checked;
GM_setValue('keepAliveSites', sites);
GM_setValue('globalEnabled', globalEnabled);
GM_notification({ text: '已保存', timeout: 1500 });
closeManagerUI();
clearTimers();
if (globalEnabled && checkSite()) initKeepAlive();
}
function createManagerUI() {
const overlay = document.createElement('div');
overlay.id = 'keepAliveOverlay';
overlay.style.cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.5);z-index:9999;display:none';
overlay.onclick = closeManagerUI;
const manager = document.createElement('div');
manager.id = 'keepAliveManager';
manager.style.cssText = 'position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);width:400px;background:white;border-radius:8px;z-index:10000;display:none';
manager.innerHTML = `
<div style="background:#3498db;color:white;padding:15px;border-radius:8px 8px 0 0;display:flex;justify-content:space-between;align-items:center">
<h3 style="margin:0">网站保活管理</h3>
<button id="closeBtn" style="background:none;border:none;color:white;font-size:20px;cursor:pointer">×</button>
</div>
<div style="padding:15px">
<div style="margin-bottom:15px">
<label><input type="checkbox" id="globalToggle" ${globalEnabled ? 'checked' : ''}> 全局启用</label>
</div>
<div style="margin-bottom:15px">
<input type="text" id="siteUrl" placeholder="输入网站URL" style="width:100%;padding:8px;border:1px solid #ddd;border-radius:4px;margin-bottom:10px">
<button id="addBtn" style="background:#3498db;color:white;border:none;padding:8px 15px;border-radius:4px;cursor:pointer;width:100%">添加网站</button>
</div>
<div id="siteList" style="max-height:300px;overflow-y:auto"></div>
</div>
<div style="padding:15px;background:#f5f5f5;border-radius:0 0 8px 8px;text-align:right">
<button id="saveBtn" style="background:#2ecc71;color:white;border:none;padding:8px 15px;border-radius:4px;cursor:pointer;margin-right:10px">保存</button>
<button id="cancelBtn" style="background:#e74c3c;color:white;border:none;padding:8px 15px;border-radius:4px;cursor:pointer">取消</button>
</div>
`;
document.body.appendChild(overlay);
document.body.appendChild(manager);
document.getElementById('closeBtn').onclick = closeManagerUI;
document.getElementById('addBtn').onclick = addSiteFromUI;
document.getElementById('saveBtn').onclick = saveConfig;
document.getElementById('cancelBtn').onclick = closeManagerUI;
renderSiteList();
}
function renderSiteList() {
const siteListDiv = document.getElementById('siteList');
if (!siteListDiv) return;
siteListDiv.innerHTML = sites.length ? '' : '<div style="text-align:center;color:#666;padding:20px">暂无网站</div>';
sites.forEach((site, index) => {
const siteItem = document.createElement('div');
siteItem.style.cssText = 'padding:10px;border-bottom:1px solid #eee;display:flex;align-items:center;justify-content:space-between';
siteItem.innerHTML = `
<div style="flex:1">
<label style="display:flex;align-items:center;cursor:pointer">
<input type="checkbox" ${site.enabled ? 'checked' : ''} style="margin-right:8px">
<span style="font-size:12px">${site.url}</span>
</label>
</div>
<button style="background:#e74c3c;color:white;border:none;padding:4px 8px;border-radius:3px;cursor:pointer;font-size:12px">删除</button>
`;
const checkbox = siteItem.querySelector('input[type="checkbox"]');
checkbox.onchange = function() {
sites[index].enabled = this.checked;
};
const deleteBtn = siteItem.querySelector('button');
deleteBtn.onclick = function() {
sites.splice(index, 1);
renderSiteList();
};
siteListDiv.appendChild(siteItem);
});
}
function clearTimers() {
clearTimeout(scrollTimer);
clearTimeout(refreshTimer);
}
function checkSite() {
const currentUrl = window.location.href;
return sites.some(site => site.enabled && new RegExp('^' + site.url.replace(/\*/g, '.*') + '$').test(currentUrl));
}
function getCurrentSiteSettings() {
const currentUrl = window.location.href;
return sites.find(site => site.enabled && new RegExp('^' + site.url.replace(/\*/g, '.*') + '$').test(currentUrl));
}
function simulateScroll() {
window.scrollBy(0, 200);
setTimeout(() => window.scrollBy(0, -100), 1000);
}
function refreshPage() {
const settings = getCurrentSiteSettings();
if (settings && Date.now() - lastActivityTime > settings.activity * 60000) {
window.location.reload();
} else {
resetTimers();
}
}
function resetTimers() {
const settings = getCurrentSiteSettings();
if (!settings) return;
lastActivityTime = Date.now();
clearTimers();
scrollTimer = setTimeout(simulateScroll, settings.scroll * 60000);
refreshTimer = setTimeout(refreshPage, settings.refresh * 60000);
}
function initKeepAlive() {
['mousemove', 'keypress', 'click', 'scroll'].forEach(event => {
document.addEventListener(event, () => { lastActivityTime = Date.now(); }, {passive: true});
});
resetTimers();
}
// 初始化
if (globalEnabled && checkSite()) {
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initKeepAlive);
} else {
initKeepAlive();
}
}
})();