// ==UserScript==
// @name 115 助力助手 - 全站通用版(支持幸运5分钟抽奖)
// @namespace http://tampermonkey.net/
// @version 1.5
// @description 在 115 全站自动完成邀请助力任务,支持许愿树、抽奖等页面
// @author You
// @match https://*.115.com/*
// @grant none
// @license MIT
// ==/UserScript==
(function () {
'use strict';
let isRunning = false;
let controller = new AbortController();
let startTime;
let completedRequests = 0;
// 防止重复加载
if (document.getElementById('boost-panel')) return;
// 检查是否在有效页面显示面板(避免在 iframe 或无操作区域显示)
function shouldShowPanel() {
const validPaths = [
'/home/',
'/social/games/lucky5',
'/act/'
];
const currentPath = window.location.pathname;
return validPaths.some(path => currentPath.includes(path)) || window.location.host.includes('f.115.com');
}
if (!shouldShowPanel()) return;
// 创建侧边栏控制按钮
const createToggleButton = () => {
const btn = document.createElement('button');
btn.id = 'boost-toggle-btn';
btn.textContent = '助力工具';
Object.assign(btn.style, {
position: 'fixed',
top: '200px',
right: '0',
width: '80px',
height: '40px',
backgroundColor: '#007bff',
color: 'white',
border: 'none',
borderRadius: '4px 0 0 4px',
cursor: 'pointer',
zIndex: '9999',
fontSize: '14px',
boxShadow: '0 2px 6px rgba(0,0,0,0.2)',
});
btn.addEventListener('click', togglePanel);
return btn;
};
// 创建主面板
const createPanel = () => {
const panel = document.createElement('div');
panel.id = 'boost-panel';
Object.assign(panel.style, {
position: 'fixed',
top: '120px',
right: '-320px',
width: '300px',
height: '600px',
backgroundColor: 'white',
border: '1px solid #ddd',
borderRadius: '8px 0 0 8px',
boxShadow: '0 4px 12px rgba(0,0,0,0.15)',
zIndex: '9999',
transition: 'right 0.3s ease',
overflow: 'hidden',
fontFamily: 'Arial, sans-serif',
});
panel.innerHTML = `
<div style="padding: 16px; background: #007bff; color: white; font-weight: bold;">
115 助力助手
</div>
<div style="padding: 16px;">
<label style="display:block;margin-bottom:8px;font-size:14px;">邀请码列表(每行一个)</label>
<textarea id="boost-codes" rows="6"
style="width:100%;font-family:monospace;font-size:12px;padding:8px;
border:1px solid #ccc;border-radius:4px;resize:none;"
placeholder="ABC123 XYZ789"></textarea>
<div id="action-buttons" style="margin-top:12px;display:flex;gap:8px;">
<button id="start-boost"
style="flex:1;background:#28a745;color:white;
border:none;padding:10px 0;border-radius:4px;font-size:14px;
cursor:pointer;">开始助力</button>
</div>
<div id="stats" style="margin-top:12px;font-size:12px;">
<div>总数: <span id="total">0</span></div>
<div style="color:green;">成功: <span id="success">0</span></div>
<div style="color:orange;">重复: <span id="duplicate">0</span></div>
<div style="color:#666;">速率: <span id="rate">0</span> req/s</div>
</div>
<div style="margin-top:16px;font-size:14px;font-weight:bold;">执行日志</div>
<div id="log-area"
style="height:200px;overflow-y:auto;border:1px solid #eee;
padding:8px;background:#f9f9f9;font-size:12px;">
<div class="log-item" style="color:#666;">等待启动...</div>
</div>
<!-- 加载动画 -->
<div id="loading" style="display:none;text-align:center;margin-top:8px;">
<div style="display:inline-block;width:16px;height:16px;border:2px solid #ddd;border-top-color:#007bff;border-radius:50%;animation:spin 1s linear infinite;"></div>
<span style="margin-left:8px;font-size:12px;color:#666;">处理中...</span>
</div>
</div>
<style>
@keyframes spin {
to { transform: rotate(360deg); }
}
</style>
`;
return panel;
};
// 添加日志(带自动滚动到底部)
function addLog(message, color = 'black') {
const logArea = document.getElementById('log-area');
const item = document.createElement('div');
item.className = 'log-item';
item.style.color = color;
item.style.margin = '4px 0';
item.style.whiteSpace = 'nowrap';
item.style.overflow = 'hidden';
item.style.textOverflow = 'ellipsis';
const time = new Date().toLocaleTimeString();
item.textContent = `[${time}] ${message}`;
logArea.appendChild(item);
// 自动滚动到底部
requestAnimationFrame(() => {
logArea.scrollTop = logArea.scrollHeight;
});
}
// 更新统计
function updateStats(key) {
const el = document.getElementById(key);
const val = parseInt(el.textContent || '0');
el.textContent = val + 1;
}
// 重置统计
function resetStats() {
document.getElementById('success').textContent = '0';
document.getElementById('duplicate').textContent = '0';
document.getElementById('rate').textContent = '0';
}
// 更新速率
function updateRate() {
if (!startTime) return;
const elapsed = (Date.now() - startTime) / 1000;
const rate = elapsed > 0 ? (completedRequests / elapsed).toFixed(1) : '0';
document.getElementById('rate').textContent = rate;
}
// 发送助力请求(带重试机制)
async function sendBoost(code, retryCount = 3) {
for (let i = 0; i < retryCount; i++) {
try {
const formData = new FormData();
formData.append('boost_code', code);
formData.append('source', 'link');
const response = await fetch('https://act.115.com/api/1.0/web/1.0/invite_boost/accept_invite', {
method: 'POST',
body: formData,
credentials: 'include',
signal: controller.signal
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const data = await response.json();
return data;
} catch (err) {
if (err.name === 'AbortError') return { state: 0, message: '请求被取消' };
if (i === retryCount - 1) {
return { state: 0, message: `网络错误(已重试${retryCount}次)` };
}
// 指数退避
await new Promise(resolve => setTimeout(resolve, 1000 * Math.pow(2, i)));
}
}
}
// 主要逻辑
async function startBoost() {
if (isRunning) return;
const textarea = document.getElementById('boost-codes');
const codes = textarea.value
.split('\n')
.map(line => line.trim().toUpperCase())
.filter(line => /^[A-Z0-9]{6}$/.test(line));
if (codes.length === 0) {
alert('请输入有效的6位邀请码(A-Z, 0-9),每行一个');
return;
}
isRunning = true;
controller = new AbortController();
startTime = Date.now();
completedRequests = 0;
// 冻结输入框和原按钮
textarea.disabled = true;
const startBtn = document.getElementById('start-boost');
if (startBtn) startBtn.style.display = 'none';
// 显示加载动画
document.getElementById('loading').style.display = 'block';
// 清除旧的按钮
const actionButtons = document.getElementById('action-buttons');
const existingStop = document.getElementById('stop-boost');
if (existingStop) existingStop.remove();
// 添加“停止”按钮
const stopBtn = document.createElement('button');
stopBtn.id = 'stop-boost';
stopBtn.textContent = '停止助力';
stopBtn.style = 'flex:1;background:#dc3545;color:white;border:none;padding:10px 0;border-radius:4px;font-size:14px;cursor:pointer;';
stopBtn.onclick = () => {
isRunning = false;
controller.abort();
addLog('🛑 用户手动停止助力', 'red');
finishProcess();
};
actionButtons.appendChild(stopBtn);
// 重置并显示总数
resetStats();
document.getElementById('total').textContent = codes.length;
// 清空日志
document.getElementById('log-area').innerHTML = '';
addLog(`共发现 ${codes.length} 个有效邀请码,开始处理...`, 'blue');
// 逐个处理
for (const code of codes) {
if (!isRunning) break;
addLog(`正在助力: ${code}`, '#007bff');
const result = await sendBoost(code);
if (result.state === 1) {
addLog(`✅ 成功助力: ${result.data.inviter_name || '未知用户'}`, 'green');
updateStats('success');
} else if (result.code === 40203004 || result.message.includes('已经')) {
addLog(`🟡 已助力过: ${code}`, 'orange');
updateStats('duplicate');
} else {
addLog(`❌ 助力失败: ${result.message || '未知错误'}`, 'red');
}
completedRequests++;
updateRate(); // 更新速率
await new Promise(resolve => {
if (!isRunning) return resolve();
setTimeout(resolve, 800);
});
}
finishProcess();
}
function finishProcess() {
isRunning = false;
const stopBtn = document.getElementById('stop-boost');
if (stopBtn) stopBtn.remove();
// 隐藏加载动画
document.getElementById('loading').style.display = 'none';
const actionButtons = document.getElementById('action-buttons');
actionButtons.innerHTML = '';
const clearBtn = document.createElement('button');
clearBtn.textContent = '清空';
clearBtn.style = 'flex:1;background:#6c757d;color:white;border:none;padding:10px 0;border-radius:4px;font-size:14px;cursor:pointer;';
clearBtn.onclick = clearAll;
const saveBtn = document.createElement('button');
saveBtn.textContent = '保存日志';
saveBtn.style = 'flex:1;background:#17a2b8;color:white;border:none;padding:10px 0;border-radius:4px;font-size:14px;cursor:pointer;';
saveBtn.onclick = saveLog;
actionButtons.appendChild(clearBtn);
actionButtons.appendChild(saveBtn);
}
function clearAll() {
const textarea = document.getElementById('boost-codes');
textarea.value = '';
textarea.disabled = false;
const logArea = document.getElementById('log-area');
logArea.innerHTML = '<div class="log-item" style="color:#666;">等待启动...</div>';
document.getElementById('total').textContent = '0';
resetStats();
const actionButtons = document.getElementById('action-buttons');
actionButtons.innerHTML = `
<button id="start-boost"
style="flex:1;background:#28a745;color:white;
border:none;padding:10px 0;border-radius:4px;font-size:14px;
cursor:pointer;">开始助力</button>
`;
document.getElementById('start-boost').addEventListener('click', startBoost, { once: false });
}
function saveLog() {
const logArea = document.getElementById('log-area');
const logs = Array.from(logArea.children)
.map(el => el.textContent)
.join('\n');
const now = new Date();
const filename = `115助力助手-${now.getFullYear()}${String(now.getMonth()+1).padStart(2,'0')}${String(now.getDate()).padStart(2,'0')}${String(now.getHours()).padStart(2,'0')}${String(now.getMinutes()).padStart(2,'0')}${String(now.getSeconds()).padStart(2,'0')}.txt`;
const blob = new Blob([logs], { type: 'text/plain;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
a.click();
URL.revokeObjectURL(url);
}
function togglePanel() {
const panel = document.getElementById('boost-panel');
if (!panel) return;
const currentRight = getComputedStyle(panel).right;
panel.style.right = currentRight === '0px' ? '-320px' : '0';
}
// 初始化函数
function init() {
if (document.getElementById('boost-panel')) return;
const toggleBtn = createToggleButton();
const panel = createPanel();
document.body.appendChild(toggleBtn);
document.body.appendChild(panel);
document.getElementById('start-boost').addEventListener('click', startBoost, { once: false });
}
// 页面加载完成后初始化
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();