// ==UserScript==
// @name 阿西吧抢
// @namespace http://tampermonkey.net/
// @version 0.5
// @description 此脚本受版权保护,禁止未经授权的修改和分发,否则负法律责任
// @author 晓星翼翼
// @license All Rights Reserved
// @copyright 2024, 晓星翼翼
// @match https://docs.qq.com/form/*
// @grant GM_setValue
// @grant GM_getValue
// ==/UserScript==
(function() {
'use strict';
let myInfo = {
name: GM_getValue('userInfo_name', ''),
class: GM_getValue('userInfo_class', ''),
number: GM_getValue('userInfo_number', '')
};
const hashedKey = '413c05ba9f2feb8109e371acd4dc0f257d150376a8fd6dba427cfc15ad56636c';
const keyExpiration = 720 * 60 * 60 * 1000;
let hasFilledForm = false;
let autoSubmitEnabled = GM_getValue('autoSubmitEnabled', false);
let autoFillEnabled = GM_getValue('autoFillEnabled', true);
let keyVerified = false;
let lastVerificationTime = GM_getValue('lastVerificationTime', 0);
let userInfoSet = GM_getValue('userInfoSet', false);
function sha256(message) {
const encoder = new TextEncoder();
const data = encoder.encode(message);
return crypto.subtle.digest('SHA-256', data)
.then(hash => {
return Array.from(new Uint8Array(hash))
.map(b => b.toString(16).padStart(2, '0'))
.join('');
});
}
function checkKeyValidity() {
const now = Date.now();
if (lastVerificationTime && (now - lastVerificationTime < keyExpiration)) {
keyVerified = true;
return true;
}
return false;
}
function checkUserInfo() {
return userInfoSet && myInfo.name && myInfo.class && myInfo.number;
}
function createInfoDialog(isFirstTime = false) {
const existingDialog = document.getElementById('userInfoDialog');
if (existingDialog) {
existingDialog.remove();
}
const dialog = document.createElement('div');
dialog.id = 'userInfoDialog';
dialog.style.cssText = `
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: white;
border: 1px solid #ccc;
padding: 20px;
border-radius: 5px;
box-shadow: 0 0 10px rgba(0,0,0,0.3);
z-index: 10000;
width: 350px;
font-family: Arial, sans-serif;
`;
dialog.innerHTML = `
<h3 style="margin-top: 0; color: #333;">${isFirstTime ? '首次设置个人信息' : '修改个人信息'}</h3>
<p style="color: #666; font-size: 14px;">请输入您的个人信息,这些信息将用于自动填写表单</p>
<div style="margin: 15px 0;">
<label style="display: block; margin-bottom: 5px; color: #333; font-size: 14px;">姓名:</label>
<input type="text" id="nameInput" value="${myInfo.name}" style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 3px; box-sizing: border-box;">
</div>
<div style="margin: 15px 0;">
<label style="display: block; margin-bottom: 5px; color: #333; font-size: 14px;">班级:</label>
<input type="text" id="classInput" value="${myInfo.class}" style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 3px; box-sizing: border-box;">
</div>
<div style="margin: 15px 0;">
<label style="display: block; margin-bottom: 5px; color: #333; font-size: 14px;">学号:</label>
<input type="text" id="numberInput" value="${myInfo.number}" style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 3px; box-sizing: border-box;">
</div>
<div style="text-align: right; margin-top: 20px;">
${!isFirstTime ? '<button id="cancelInfoButton" style="background: #f5f5f5; border: 1px solid #ddd; padding: 8px 16px; margin-right: 10px; border-radius: 3px; cursor: pointer;">取消</button>' : ''}
<button id="saveInfoButton" style="background: #4285f4; color: white; border: none; padding: 8px 16px; border-radius: 3px; cursor: pointer;">保存</button>
</div>
<p id="infoError" style="color: red; font-size: 12px; margin-top: 10px; display: none;">请填写完整的信息</p>
`;
document.body.appendChild(dialog);
const overlay = document.createElement('div');
overlay.id = 'infoDialogOverlay';
overlay.style.cssText = `
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
z-index: 9999;
`;
document.body.appendChild(overlay);
if (!isFirstTime) {
document.getElementById('cancelInfoButton').addEventListener('click', () => {
dialog.remove();
overlay.remove();
});
}
document.getElementById('saveInfoButton').addEventListener('click', () => saveUserInfo(isFirstTime));
[document.getElementById('nameInput'), document.getElementById('classInput'), document.getElementById('numberInput')].forEach(input => {
input.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
saveUserInfo(isFirstTime);
}
});
});
setTimeout(() => {
document.getElementById('nameInput').focus();
}, 100);
}
function saveUserInfo(isFirstTime = false) {
const nameInput = document.getElementById('nameInput');
const classInput = document.getElementById('classInput');
const numberInput = document.getElementById('numberInput');
const errorMsg = document.getElementById('infoError');
const name = nameInput.value.trim();
const className = classInput.value.trim();
const number = numberInput.value.trim();
// 验证必填字段
if (!name || !className || !number) {
errorMsg.style.display = 'block';
return;
}
// 保存信息
myInfo = { name, class: className, number };
GM_setValue('userInfo_name', name);
GM_setValue('userInfo_class', className);
GM_setValue('userInfo_number', number);
GM_setValue('userInfoSet', true);
userInfoSet = true;
// 关闭对话框
document.getElementById('userInfoDialog').remove();
const overlay = document.getElementById('infoDialogOverlay');
if (overlay) overlay.remove();
// 显示成功消息
updateStatus(`个人信息已保存:${name} - ${className} - ${number}`, true);
// 如果是首次设置,继续初始化流程
if (isFirstTime) {
setTimeout(() => {
if (checkKeyValidity()) {
init();
addControlButtons();
} else {
createKeyDialog();
}
}, 1000);
} else {
// 重置填写状态,允许重新填写
hasFilledForm = false;
}
}
function createKeyDialog() {
const existingDialog = document.getElementById('keyVerificationDialog');
if (existingDialog) {
existingDialog.remove();
}
const dialog = document.createElement('div');
dialog.id = 'keyVerificationDialog';
dialog.style.cssText = `
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: white;
border: 1px solid #ccc;
padding: 20px;
border-radius: 5px;
box-shadow: 0 0 10px rgba(0,0,0,0.3);
z-index: 10000;
width: 300px;
font-family: Arial, sans-serif;
`;
dialog.innerHTML = `
<h3 style="margin-top: 0; color: #333;">请输入密钥</h3>
<p style="color: #666; font-size: 14px;">请输入正确的密钥以使用此脚本功能</p>
<input type="password" id="keyInput" style="width: 100%; padding: 8px; margin: 10px 0; border: 1px solid #ddd; border-radius: 3px; box-sizing: border-box;">
<div style="text-align: right;">
<button id="cancelKeyButton" style="background: #f5f5f5; border: 1px solid #ddd; padding: 5px 10px; margin-right: 10px; border-radius: 3px; cursor: pointer;">取消</button>
<button id="submitKeyButton" style="background: #4285f4; color: white; border: none; padding: 5px 10px; border-radius: 3px; cursor: pointer;">确认</button>
</div>
<p id="keyError" style="color: red; font-size: 12px; margin-top: 10px; display: none;">密钥错误,请重试</p>
`;
document.body.appendChild(dialog);
// 添加背景遮罩
const overlay = document.createElement('div');
overlay.id = 'keyDialogOverlay';
overlay.style.cssText = `
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
z-index: 9999;
`;
document.body.appendChild(overlay);
document.getElementById('cancelKeyButton').addEventListener('click', () => {
dialog.remove();
overlay.remove();
});
document.getElementById('submitKeyButton').addEventListener('click', verifyKey);
document.getElementById('keyInput').addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
verifyKey();
}
});
setTimeout(() => {
document.getElementById('keyInput').focus();
}, 100);
}
function verifyKey() {
const keyInput = document.getElementById('keyInput');
const errorMsg = document.getElementById('keyError');
sha256(keyInput.value).then(inputHash => {
if (inputHash === hashedKey) {
keyVerified = true;
lastVerificationTime = Date.now();
GM_setValue('lastVerificationTime', lastVerificationTime);
document.getElementById('keyVerificationDialog').remove();
document.getElementById('keyDialogOverlay').remove();
init();
addControlButtons();
updateStatus('密钥验证成功!', true);
} else {
errorMsg.style.display = 'block';
keyInput.value = '';
keyInput.focus();
}
}).catch(error => {
console.error('哈希计算错误:', error);
errorMsg.textContent = '验证过程中出错,请重试';
errorMsg.style.display = 'block';
});
}
function createFloatingWindow() {
const floatingWindow = document.createElement('div');
floatingWindow.id = 'autoFillStatus';
floatingWindow.style.cssText = `
position: fixed;
top: 135px;
right: 250px;
background-color: rgba(0, 0, 0, 0.8);
color: white;
padding: 10px;
border-radius: 5px;
z-index: 9999;
font-size: 14px;
min-width: 200px;
box-shadow: 0 2px 10px rgba(0,0,0,0.2);
opacity: 0.3;
transition: opacity 0.3s;
`;
document.body.appendChild(floatingWindow);
floatingWindow.onmouseenter = () => { floatingWindow.style.opacity = '1'; };
floatingWindow.onmouseleave = () => { floatingWindow.style.opacity = '0.3'; };
return floatingWindow;
}
function updateStatus(message, success = true) {
const statusDiv = document.getElementById('autoFillStatus') || createFloatingWindow();
statusDiv.innerHTML = `
<div style="margin-bottom: 5px;">自动填写状态:</div>
<div style="color: ${success ? '#4CAF50' : '#FF5252'}">${message}</div>
<div style="font-size: 12px; margin-top: 5px; color: #ccc;">当前信息: ${myInfo.name} - ${myInfo.class}</div>
`;
}
function findInputsByText() {
const allInputs = Array.from(document.querySelectorAll('.form-simple-main textarea'));
const result = {
nameInput: null,
classInput: null,
numberInput: null
};
allInputs.forEach(textarea => {
let questionText = '';
let currentElement = textarea;
while (currentElement && !questionText) {
const prevElement = currentElement.previousElementSibling;
if (prevElement) {
questionText = prevElement.textContent.trim();
}
currentElement = currentElement.parentElement;
}
if (questionText.includes('01') || questionText.includes('姓名')) {
result.nameInput = textarea;
}
if (questionText.includes('02') || questionText.includes('班级')) {
result.classInput = textarea;
}
if (questionText.includes('03') || questionText.includes('学号')) {
result.numberInput = textarea;
}
});
return result;
}
function simulateUserInput(element, value) {
element.focus();
element.value = '';
element.dispatchEvent(new Event('input', { bubbles: true }));
element.value = value;
element.dispatchEvent(new InputEvent('input', {
bubbles: true,
cancelable: true,
inputType: 'insertText',
data: value
}));
element.dispatchEvent(new Event('change', { bubbles: true }));
element.blur();
element.dispatchEvent(new Event('focusout', { bubbles: true }));
element.dispatchEvent(new Event('keyup', { bubbles: true }));
element.dispatchEvent(new Event('keydown', { bubbles: true }));
}
function autoSubmit() {
const submitButton = document.querySelector('button[type="button"]');
if (submitButton) {
submitButton.click();
setTimeout(() => {
const confirmButton = document.querySelector('.dui-modal-footer-ok');
if (confirmButton) {
confirmButton.click();
updateStatus('表单已自动提交!');
} else {
updateStatus('未找到确认按钮', false);
}
}, 50);
} else {
updateStatus('未找到提交按钮', false);
}
}
function fillForm() {
if (hasFilledForm) {
return;
}
const inputs = findInputsByText();
let availableFields = 0;// 计算实际可用的输入框数量
let filledCount = 0;
if (inputs.nameInput) availableFields++;
if (inputs.classInput) availableFields++;
if (inputs.numberInput) availableFields++;
if (availableFields === 0) {
updateStatus('未找到任何输入框', false);
return;
}
function checkAndSubmit() {
if (filledCount === availableFields) { // 使用实际的字段数量进行比较
updateStatus('所有可用字段填写完成!');
hasFilledForm = true;
if (autoSubmitEnabled) {
setTimeout(autoSubmit, 50);
}
}
}
if (inputs.nameInput) {
setTimeout(() => {
simulateUserInput(inputs.nameInput, myInfo.name);
filledCount++;
checkAndSubmit();
}, 10);
}
if (inputs.classInput) {
setTimeout(() => {
simulateUserInput(inputs.classInput, myInfo.class);
filledCount++;
checkAndSubmit();
}, 10);
}
if (inputs.numberInput) {
setTimeout(() => {
simulateUserInput(inputs.numberInput, myInfo.number);
filledCount++;
checkAndSubmit();
}, 10);
}
if (availableFields === 0) {
updateStatus('未找到任何匹配的输入框', false);
}
}
function addControlButtons() {
const buttonContainer = document.createElement('div');
buttonContainer.id = 'controlButtonsContainer';
buttonContainer.style.cssText = `
position: fixed;
top: 200px;
right: 300px;
display: flex;
flex-direction: column;
gap: 10px;
z-index: 9999;
`;
const fillButton = document.createElement('button');
fillButton.innerHTML = `填写模式: ${autoFillEnabled ? '自动' : '手动'}`;
fillButton.style.cssText = `
padding: 5px 10px;
background-color: ${autoFillEnabled ? '#FFA500' : '#2196F3'};
color: white;
border: none;
border-radius: 3px;
cursor: pointer;
opacity: 0.3;
transition: opacity 0.3s;
`;
const submitToggle = document.createElement('button');
submitToggle.innerHTML = `自动提交: ${autoSubmitEnabled ? '开启' : '关闭'}`;
submitToggle.style.cssText = `
padding: 5px 10px;
background-color: ${autoSubmitEnabled ? '#4CAF50' : '#FF5252'};
color: white;
border: none;
border-radius: 3px;
cursor: pointer;
opacity: 0.3;
transition: opacity 0.30s;
`;
const submitNowButton = document.createElement('button');
submitNowButton.innerHTML = '立即提交';
submitNowButton.style.cssText = `
padding: 5px 10px;
background-color: #2196F3;
color: white;
border: none;
border-radius: 3px;
cursor: pointer;
opacity: 0.3;
transition: opacity 0.30s;
`;
const editInfoButton = document.createElement('button');
editInfoButton.innerHTML = '修改信息';
editInfoButton.style.cssText = `
padding: 5px 10px;
background-color: #9C27B0;
color: white;
border: none;
border-radius: 3px;
cursor: pointer;
opacity: 0.3;
transition: opacity 0.30s;
`;
const resetKeyButton = document.createElement('button');
resetKeyButton.innerHTML = '重置密钥';
resetKeyButton.style.cssText = `
padding: 5px 10px;
background-color: #FF9800;
color: white;
border: none;
border-radius: 3px;
cursor: pointer;
opacity: 0.3;
transition: opacity 0.30s;
`;
// 悬浮窗效果
[fillButton, submitToggle, submitNowButton, editInfoButton, resetKeyButton].forEach(button => {
button.onmouseenter = () => { button.style.opacity = '1'; };
button.onmouseleave = () => { button.style.opacity = '0.3'; };
});
// 模式切换
fillButton.onclick = () => {
autoFillEnabled = !autoFillEnabled;
GM_setValue('autoFillEnabled', autoFillEnabled); // 使用 GM_setValue 保存设置
fillButton.innerHTML = `填写模式: ${autoFillEnabled ? '自动' : '手动'}`;
fillButton.style.backgroundColor = autoFillEnabled ? '#FFA500' : '#2196F3';
updateStatus(`当前模式: ${autoFillEnabled ? '自动填写' : '手动填写'}`);
// 自动模式切换后立马执行
if (autoFillEnabled && !hasFilledForm) {
fillForm();
}
};
submitToggle.onclick = () => {
autoSubmitEnabled = !autoSubmitEnabled;
GM_setValue('autoSubmitEnabled', autoSubmitEnabled);// 使用 GM_setValue 保存设置
submitToggle.innerHTML = `自动提交: ${autoSubmitEnabled ? '开启' : '关闭'}`;
submitToggle.style.backgroundColor = autoSubmitEnabled ? '#4CAF50' : '#FF5252';
if (autoSubmitEnabled && hasFilledForm) {
autoSubmit();
}
};
submitNowButton.onclick = () => {
autoSubmit();
};
editInfoButton.onclick = () => {
createInfoDialog(false);
};
resetKeyButton.onclick = () => {
// 重置密钥验证状态
keyVerified = false;
GM_setValue('lastVerificationTime', 0);
// 移除控制按钮
document.getElementById('controlButtonsContainer').remove();
// 重新显示密钥验证对话框
createKeyDialog();
updateStatus('已重置密钥验证状态,请重新验证', false);
};
buttonContainer.appendChild(fillButton);
buttonContainer.appendChild(submitToggle);
buttonContainer.appendChild(submitNowButton);
buttonContainer.appendChild(editInfoButton);
buttonContainer.appendChild(resetKeyButton);
document.body.appendChild(buttonContainer);
}
function init() {
if (!document.getElementById('autoFillStatus')) {
createFloatingWindow();
}
if (document.readyState === 'complete') {
updateStatus('正在查找表单...');
if (autoFillEnabled) {
fillForm();
} else {
updateStatus('当前为手动填写模式');
}
} else {
updateStatus('等待页面加载...', false);
setTimeout(init, 10);
}
}
// 主入口
setTimeout(() => {
// 首先检查是否已设置个人信息
if (!checkUserInfo()) {
createFloatingWindow();
updateStatus('首次使用,请设置个人信息', false);
createInfoDialog(true);
return;
}
// 检查是否已验证过密钥且在有效期内
if (checkKeyValidity()) {
init();
addControlButtons();
updateStatus('密钥已验证,脚本已启动', true);
} else {
createFloatingWindow();
updateStatus('请输入密钥验证', false);
createKeyDialog();
}
}, 100);
// 额外检查
setTimeout(() => {
if (keyVerified && !hasFilledForm && document.getElementById('autoFillStatus')) {
init();
}
}, 200);
})();