用户:EditBot/console
来自植物大战僵尸杂交版Wiki
更多操作
🤖 EditBot 控制台
● 运行中
<button id="bot-toggle" style="background:#ff4444;border:none;color:#fff;padding:2px 10px;border-radius:4px;cursor:pointer;font-size:11px;">停止</button>
<button id="bot-close" style="background:#555;border:none;color:#fff;padding:2px 8px;border-radius:4px;cursor:pointer;font-size:11px;">✕</button>
用户: 加载中... 检测: 0 人 写入: 0 次
⏳ 等待启动...
<input id="bot-interval" type="number" value="3" style="width:50px;background:#222;border:1px solid #444;color:#fff;border-radius:4px;padding:2px 4px;font-size:11px;">
秒
<button id="bot-scan" style="background:#0088ff;border:none;color:#fff;padding:2px 12px;border-radius:4px;cursor:pointer;font-size:11px;">立即扫描</button>
<button id="bot-clear" style="background:#555;border:none;color:#fff;padding:2px 12px;border-radius:4px;cursor:pointer;font-size:11px;">清空日志</button>
<style>
- bot-log::-webkit-scrollbar { width: 4px; }
- bot-log::-webkit-scrollbar-track { background: #111; }
- bot-log::-webkit-scrollbar-thumb { background: #444; border-radius: 2px; }
- bot-console .log-success { color: #00ff00; }
- bot-console .log-warning { color: #ffaa00; }
- bot-console .log-error { color: #ff4444; }
- bot-console .log-info { color: #66ccff; }
- bot-console .log-editor { color: #ff66aa; }
</style>
<script> (function() {
'use strict';
// ============ 配置 ============
var BOT_NAME = mw.config.get('wgUserName') || 'EditBot';
var CHECK_INTERVAL = 3000;
var running = false;
var timer = null;
var writeCount = 0;
var scanCount = 0;
var detectedEditors = {};
var logLines = [];
// ============ DOM 引用 ============
var logEl = document.getElementById('bot-log');
var statusEl = document.getElementById('bot-status');
var toggleBtn = document.getElementById('bot-toggle');
var countEl = document.getElementById('bot-count');
var writesEl = document.getElementById('bot-writes');
var userEl = document.getElementById('bot-user');
var intervalInput = document.getElementById('bot-interval');
userEl.textContent = BOT_NAME;
// ============ 日志系统 ============
function log(msg, type) {
type = type || 'info';
var time = new Date().toLocaleTimeString();
var colors = {
success: '#00ff00',
warning: '#ffaa00',
error: '#ff4444',
info: '#66ccff',
editor: '#ff66aa',
mute: '#555'
};
var color = colors[type] || '#ffffff';
var line = '
[' + time + '] ' + msg + '
';
logEl.innerHTML += line;
logEl.scrollTop = logEl.scrollHeight;
if (logEl.children.length > 500) {
logEl.removeChild(logEl.firstChild);
}
}
// ============ 核心检测 ============
function scanAllEditors() {
if (!running) return;
var api = new mw.Api();
scanCount++;
api.get({
action: 'query',
list: 'recentchanges',
rclimit: 200,
rcprop: 'user|title|timestamp',
rcshow: '!bot',
rcnamespace: 0,
format: 'json'
}).then(function(data) {
var pageMap = {};
var activeUsers = new Set();
if (data.query && data.query.recentchanges) {
data.query.recentchanges.forEach(function(rc) {
var title = rc.title;
var user = rc.user;
var timeDiff = Date.now() - new Date(rc.timestamp).getTime();
if (timeDiff < 60000 && user !== BOT_NAME) {
if (!pageMap[title]) pageMap[title] = new Set();
pageMap[title].add(user);
activeUsers.add(user);
}
});
}
var totalEditors = 0;
var writePromises = [];
for (var page in pageMap) {
var editors = Array.from(pageMap[page]);
totalEditors += editors.length;
editors.forEach(function(editor) {
if (editor === BOT_NAME) return;
var otherEditors = editors.filter(function(e) {
return e !== editor && e !== BOT_NAME;
});
writePromises.push(writeUserStatus(editor, page, otherEditors));
});
}
// 清理不在线的用户状态
var cleanupPromise = cleanupOldStatus(activeUsers);
Promise.all(writePromises).then(function() {
countEl.textContent = totalEditors;
if (totalEditors > 0) {
var editorList = Array.from(activeUsers).slice(0, 5).join('、');
log('📊 检测到 ' + totalEditors + ' 位活跃用户:' + editorList + (activeUsers.size > 5 ? ' ...' : ), 'editor');
} else {
log('📊 暂无活跃用户', 'mute');
}
}).catch(function(err) {
log('⚠️ 写入失败: ' + err.message, 'warning');
});
}).fail(function(err) {
log('❌ 扫描失败: ' + (err.error?.info || err.message || '未知错误'), 'error');
});
}
// ============ 写入用户状态 ============
function writeUserStatus(username, page, editors) {
return new Promise(function(resolve, reject) {
var statusPage = 'User:' + username + '/edit-status';
var api = new mw.Api();
var statusData = {
page: page,
editors: editors.map(function(e) {
return { user: e, timestamp: Date.now() };
}),
timestamp: Date.now(),
checkedBy: BOT_NAME,
status: editors.length > 0 ? 'editing' : 'idle'
};
api.postWithEditToken({
action: 'edit',
title: statusPage,
text: JSON.stringify(statusData, null, 2),
summary: '🤖 ' + BOT_NAME + ' 检测编辑状态',
minor: true,
bot: true
}).then(function() {
writeCount++;
writesEl.textContent = writeCount;
resolve();
}).fail(function(err) {
reject(err);
});
});
}
// ============ 清理过期状态 ============
function cleanupOldStatus(activeUsers) {
return new Promise(function(resolve) {
var api = new mw.Api();
api.get({
action: 'query',
list: 'allpages',
apnamespace: 2,
apprefix: 'edit-status',
aplimit: 100,
format: 'json'
}).then(function(data) {
if (!data.query || !data.query.allpages) {
resolve();
return;
}
var promises = [];
data.query.allpages.forEach(function(page) {
var username = page.title.replace('User:', ).replace('/edit-status', );
if (username === BOT_NAME || activeUsers.has(username)) return;
var p = new mw.Api().get({
action: 'query',
titles: page.title,
prop: 'revisions',
rvprop: 'content',
rvlimit: 1,
format: 'json'
}).then(function(contentData) {
var pages = contentData.query.pages;
for (var id in pages) {
if (pages[id].revisions && pages[id].revisions[0]) {
try {
var status = JSON.parse(pages[id].revisions[0]['*']);
if (status.status === 'editing' && (Date.now() - status.timestamp) > 60000) {
var api2 = new mw.Api();
var statusData = {
page: status.page || ,
editors: [],
timestamp: Date.now(),
checkedBy: BOT_NAME,
status: 'idle'
};
return api2.postWithEditToken({
action: 'edit',
title: page.title,
text: JSON.stringify(statusData, null, 2),
summary: '🤖 ' + BOT_NAME + ' 清理过期状态',
minor: true,
bot: true
});
}
} catch(e) {}
}
}
return Promise.resolve();
});
promises.push(p);
});
Promise.all(promises).then(resolve);
}).fail(function() { resolve(); });
});
}
// ============ 执行一轮 ============
function doRound() {
if (!running) return;
log('🔄 扫描中...', 'info');
scanAllEditors();
}
// ============ 控制功能 ============
function startBot() {
if (running) return;
running = true;
var interval = parseInt(intervalInput.value) || 3;
CHECK_INTERVAL = interval * 1000;
statusEl.textContent = '● 运行中';
statusEl.style.color = '#00ff00';
toggleBtn.textContent = '停止';
toggleBtn.style.background = '#ff4444';
log('✅ 机器人已启动,间隔 ' + interval + ' 秒', 'success');
doRound();
timer = setInterval(doRound, CHECK_INTERVAL);
}
function stopBot() {
if (!running) return;
running = false;
if (timer) {
clearInterval(timer);
timer = null;
}
statusEl.textContent = '● 已停止';
statusEl.style.color = '#ff4444';
toggleBtn.textContent = '启动';
toggleBtn.style.background = '#44aa44';
log('⏹️ 机器人已停止', 'warning');
}
function toggleBot() {
if (running) {
stopBot();
} else {
startBot();
}
}
// ============ 拖拽支持 ============
var consoleEl = document.getElementById('bot-console');
var isDragging = false;
var startX, startY, origX, origY;
consoleEl.querySelector('div:first-child').addEventListener('mousedown', function(e) {
if (e.target.tagName === 'BUTTON') return;
isDragging = true;
startX = e.clientX;
startY = e.clientY;
origX = consoleEl.offsetLeft;
origY = consoleEl.offsetTop;
consoleEl.style.cursor = 'grabbing';
e.preventDefault();
});
document.addEventListener('mousemove', function(e) {
if (!isDragging) return;
var dx = e.clientX - startX;
var dy = e.clientY - startY;
consoleEl.style.left = (origX + dx) + 'px';
consoleEl.style.top = (origY + dy) + 'px';
consoleEl.style.right = 'auto';
consoleEl.style.bottom = 'auto';
});
document.addEventListener('mouseup', function() {
isDragging = false;
consoleEl.style.cursor = 'default';
});
// ============ 按钮绑定 ============
document.getElementById('bot-toggle').addEventListener('click', toggleBot);
document.getElementById('bot-close').addEventListener('click', function() {
if (running) stopBot();
consoleEl.style.display = 'none';
log('📦 控制台已隐藏,重新打开请运行 showBot()', 'info');
});
document.getElementById('bot-scan').addEventListener('click', function() {
if (running) {
log('🔄 手动扫描...', 'info');
doRound();
} else {
log('⚠️ 机器人已停止,请先启动', 'warning');
}
});
document.getElementById('bot-clear').addEventListener('click', function() {
logEl.innerHTML = ;
log('🗑️ 日志已清空', 'info');
});
intervalInput.addEventListener('change', function() {
var val = parseInt(this.value) || 3;
if (val < 1) this.value = 1;
if (val > 30) this.value = 30;
if (running) {
log('⏱️ 间隔已改为 ' + this.value + ' 秒,重启生效', 'info');
stopBot();
startBot();
}
});
// ============ 暴露全局 ============
window.showBot = function() {
consoleEl.style.display = 'block';
log('📦 控制台已显示', 'info');
};
window.hideBot = function() {
consoleEl.style.display = 'none';
};
// ============ 自动启动 ============
log('🤖 ' + BOT_NAME + ' 控制台已加载', 'success');
log('💡 点击 "启动" 开始检测,或拖拽移动控制台', 'info');
// 自动启动 startBot();
})(); </script>