// ==UserScript==
// @name 🚀速星-问卷星自动答题助手 | 🔥可视化比例配置 | 🔓破解复制限制 | 📊批量循环提交 | 🤖辅助绕过验证
// @namespace https://wjx.panel.local
// @version 4.1.1
// @description 一款功能强大的问卷星辅助工具。支持全题型识别(单选、多选、填空、矩阵、量表、地区、排序),通过可视化面板自定义各选项权重及设计份数。支持一键破解复制限制,全自动模拟填写与批量提交,内置滑块验证模拟及本地存储清理功能。
// @author x7R2p9
// @match https://*.wjx.top/*
// @match https://*.wjx.cn/*
// @match https://*.wjx.com/*
// @grant none
// @run-at document-end
// @license MIT
// @antifeature ads 脚本配置面板中包含第三方问卷服务的推荐链接
// @tag 问卷星
// @tag 问卷星脚本
// @tag 问卷星全自动
// @tag 自动填写
// @tag 批量提交
// @tag 问卷星刷问卷
// @tag 问卷星辅助
// ==/UserScript==
(function() {
'use strict';
const PANEL_ID = 'wjx-commercial-panel';
const STORAGE_PREFIX = 'WJX_CN_PANEL_';
const REMAINING_COUNT_KEY = 'WJX_REMAINING_COUNT';
const SURVEY_URL_KEY = 'WJX_SURVEY_URL';
const GREASYFORK_URL = 'https://greasyfork.org/zh-CN/scripts/569375-速星-问卷星自动答题助手-可视化比例配置-破解复制限制-批量循环提交-辅助绕过验证/';
const PROMO_TOAST_KEY = 'WJX_GREASYFORK_PROMO_SHOWN';
const DEFAULT_TEXT_LIBRARY = ['很好', '满意', '支持', '体验不错'];
const TYPE_LABELS = {
radio: '单选题',
checkbox: '多选题',
dropdown: '下拉题',
text: '填空题',
rating: '量表题',
matrix: '矩阵题',
location: '地区题',
sorting: '排序题',
unknown: '未知题型'
};
const State = {
surveyKey: '',
config: {
targetCount: 1,
questions: []
}
};
const STYLE_TEXT = `
:root {
--wjx-bg: #f5f9ff;
--wjx-surface: #ffffff;
--wjx-surface-strong: #ffffff;
--wjx-primary: #0064ff;
--wjx-primary-deep: #0052d9;
--wjx-primary-soft: #edf4ff;
--wjx-line: #d7e6ff;
--wjx-text: #1f2d3d;
--wjx-subtle: #5f6f82;
--wjx-shadow: 0 10px 28px rgba(23, 80, 179, 0.10);
}
#${PANEL_ID} {
position: fixed;
top: 16px;
right: 16px;
width: 402px;
max-height: calc(100vh - 32px);
z-index: 2147483647;
display: flex;
flex-direction: column;
overflow: hidden;
border-radius: 10px;
border: 1px solid var(--wjx-line);
background: var(--wjx-surface);
color: var(--wjx-text);
box-shadow: var(--wjx-shadow);
font-family: "PingFang SC", "Microsoft YaHei", "Noto Sans SC", sans-serif;
}
#${PANEL_ID}.is-collapsed {
width: 240px;
}
#${PANEL_ID}.is-collapsed .wjx-ad-copy {
display: block;
margin-top: 8px;
padding: 0;
border: 0;
background: transparent;
box-shadow: none;
color: var(--wjx-subtle);
font-size: 12px;
line-height: 1.6;
}
#${PANEL_ID}.is-collapsed .wjx-ad-copy span {
display: inline;
flex: none;
min-width: auto;
}
#${PANEL_ID}.is-collapsed .wjx-ad-copy a {
display: inline;
margin-left: 6px;
padding: 0;
border: 0;
border-radius: 0;
background: transparent;
box-shadow: none;
color: #d93025;
}
.wjx-head {
padding: 16px;
background: linear-gradient(180deg, #f8fbff, #edf4ff);
color: var(--wjx-text);
border-bottom: 1px solid var(--wjx-line);
border-top: 3px solid var(--wjx-primary);
}
.wjx-head-top {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 12px;
}
.wjx-title {
margin: 0;
font-size: 18px;
font-weight: 700;
letter-spacing: 0;
}
.wjx-ad-copy {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
margin-top: 12px;
padding: 12px 14px;
border-radius: 8px;
border: 1px solid #ffd089;
background: linear-gradient(180deg, #fff7db, #ffefbf);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.75);
font-size: 12px;
line-height: 1.6;
color: #7a4b00;
}
.wjx-ad-copy span {
flex: 1;
min-width: 0;
}
.wjx-ad-copy a {
text-decoration: none;
font-weight: 700;
color: #ffffff;
background: linear-gradient(180deg, #ff5a3d, #e23a22);
border: 1px solid #d9341d;
border-radius: 6px;
padding: 6px 12px;
white-space: nowrap;
box-shadow: 0 4px 10px rgba(226, 58, 34, 0.22);
}
.wjx-ad-copy-bottom {
display: block;
margin-top: 0;
padding: 0;
border: 0;
background: transparent;
box-shadow: none;
color: var(--wjx-subtle);
font-size: 12px;
line-height: 1.6;
}
.wjx-ad-copy-bottom span {
display: inline;
flex: none;
min-width: auto;
}
.wjx-ad-copy-bottom a {
display: inline;
margin-left: 6px;
padding: 0;
border: 0;
border-radius: 0;
background: transparent;
box-shadow: none;
color: #d93025;
}
.wjx-toggle {
border: 1px solid #b9d2ff;
border-radius: 6px;
padding: 6px 12px;
font-size: 11px;
font-weight: 700;
color: var(--wjx-primary-deep);
background: #ffffff;
cursor: pointer;
flex-shrink: 0;
}
.wjx-body {
padding: 16px;
overflow-y: auto;
background: var(--wjx-bg);
}
.wjx-section {
margin-bottom: 14px;
padding: 14px;
border-radius: 8px;
background: var(--wjx-surface);
border: 1px solid var(--wjx-line);
}
.wjx-section-title {
margin: 0 0 10px;
font-size: 13px;
font-weight: 700;
color: var(--wjx-primary-deep);
}
.wjx-metrics {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 10px;
}
.wjx-metric {
padding: 12px;
border-radius: 8px;
background: var(--wjx-surface-strong);
border: 1px solid var(--wjx-line);
}
.wjx-metric span {
display: block;
}
.wjx-metric-label {
font-size: 11px;
color: var(--wjx-subtle);
margin-bottom: 4px;
}
.wjx-metric-value {
font-size: 18px;
font-weight: 800;
}
.wjx-field {
display: flex;
flex-direction: column;
gap: 6px;
margin-bottom: 10px;
}
.wjx-field label {
font-size: 12px;
font-weight: 700;
}
.wjx-input,
.wjx-textarea {
width: 100%;
box-sizing: border-box;
border: 1px solid #c9dbff;
border-radius: 6px;
padding: 10px 12px;
font-size: 13px;
color: var(--wjx-text);
background: #ffffff;
outline: none;
}
.wjx-input:focus,
.wjx-textarea:focus {
border-color: #7fb0ff;
box-shadow: 0 0 0 3px rgba(0,100,255,0.10);
}
.wjx-textarea {
min-height: 88px;
resize: vertical;
line-height: 1.5;
}
.wjx-actions {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 8px;
}
.wjx-btn {
min-height: 40px;
border-radius: 6px;
padding: 0 12px;
font-size: 13px;
font-weight: 700;
cursor: pointer;
transition: background-color 0.18s ease, border-color 0.18s ease, color 0.18s ease;
}
.wjx-btn:hover {
filter: none;
}
.wjx-btn-primary {
color: #ffffff;
background: var(--wjx-primary);
border: 1px solid var(--wjx-primary);
}
.wjx-btn-secondary {
color: var(--wjx-text);
background: #ffffff;
border: 1px solid #c9dbff;
}
.wjx-list {
display: flex;
flex-direction: column;
gap: 12px;
}
.wjx-card {
padding: 14px;
border-radius: 8px;
background: var(--wjx-surface-strong);
border: 1px solid var(--wjx-line);
}
.wjx-card-head {
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 12px;
margin-bottom: 8px;
}
.wjx-card-title {
font-size: 13px;
font-weight: 800;
line-height: 1.5;
}
.wjx-card-type {
flex-shrink: 0;
padding: 5px 9px;
border-radius: 6px;
background: var(--wjx-primary-soft);
color: var(--wjx-primary-deep);
font-size: 11px;
font-weight: 700;
border: 1px solid #cfe0ff;
}
.wjx-card-meta {
margin-bottom: 10px;
color: var(--wjx-subtle);
font-size: 11px;
line-height: 1.45;
}
.wjx-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 8px;
}
.wjx-grid-item {
padding: 8px;
border-radius: 6px;
background: #f7fbff;
border: 1px solid #deebff;
}
.wjx-grid-item span {
display: block;
margin-bottom: 6px;
font-size: 11px;
color: var(--wjx-subtle);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.wjx-grid-item input {
width: 100%;
box-sizing: border-box;
border: 1px solid #c9dbff;
border-radius: 6px;
padding: 8px 10px;
font-size: 13px;
background: #ffffff;
}
.wjx-tools {
display: flex;
gap: 6px;
flex-wrap: wrap;
margin-top: 10px;
}
.wjx-chip {
border: 1px solid #cfe0ff;
border-radius: 6px;
padding: 6px 10px;
font-size: 11px;
font-weight: 700;
color: var(--wjx-primary-deep);
background: var(--wjx-primary-soft);
cursor: pointer;
}
.wjx-empty {
text-align: center;
padding: 24px 16px;
color: var(--wjx-subtle);
line-height: 1.7;
}
.wjx-toast {
position: fixed;
right: 24px;
bottom: 24px;
z-index: 2147483647;
max-width: 320px;
padding: 12px 14px;
border-radius: 8px;
color: #ffffff;
background: rgba(0, 82, 217, 0.94);
box-shadow: 0 12px 28px rgba(0, 82, 217, 0.20);
font-size: 13px;
line-height: 1.5;
opacity: 0;
transform: translateY(8px);
transition: opacity 0.18s ease, transform 0.18s ease;
pointer-events: none;
}
.wjx-toast.is-actionable {
max-width: 300px;
padding: 10px 12px;
font-size: 12px;
line-height: 1.6;
background: rgba(19, 50, 104, 0.96);
box-shadow: 0 10px 24px rgba(19, 50, 104, 0.18);
pointer-events: auto;
cursor: pointer;
}
.wjx-toast .wjx-toast-link {
color: #ffd36b;
font-weight: 700;
}
.wjx-toast.is-visible {
opacity: 1;
transform: translateY(0);
}
.wjx-hidden {
display: none;
}
@media (max-width: 640px) {
#${PANEL_ID} {
top: auto;
right: 10px;
left: 10px;
bottom: 10px;
width: auto;
max-height: 82vh;
}
.wjx-metrics,
.wjx-actions,
.wjx-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.wjx-ad-copy {
flex-direction: column;
align-items: flex-start;
}
}
`;
function init() {
// 破解右键、选择和复制限制
try {
document.oncontextmenu = () => true;
document.onselectstart = () => true;
document.oncopy = () => true;
document.onpaste = () => true;
if (window.$ && window.$.fn) {
$('body').css('user-select', 'text');
}
} catch (e) {}
if (isCompletionPage()) {
handleCompletion();
return;
}
if (document.getElementById(PANEL_ID)) {
return;
}
State.surveyKey = createSurveyKey();
injectStyles();
renderPanel();
refreshQuestions({ silent: true, preserveInputs: false });
scheduleRescan();
checkAndContinueAutomation();
schedulePromoToast();
}
function isCompletionPage() {
return /complete\.aspx|join\/complete|done/i.test(location.href);
}
function handleCompletion() {
let remaining = Number(localStorage.getItem(REMAINING_COUNT_KEY) || 0);
if (remaining > 0) {
remaining -= 1;
localStorage.setItem(REMAINING_COUNT_KEY, String(remaining));
if (remaining > 0) {
const url = localStorage.getItem(SURVEY_URL_KEY);
if (url) {
setTimeout(() => {
location.href = url;
}, 2000);
}
} else {
localStorage.removeItem(REMAINING_COUNT_KEY);
localStorage.removeItem(SURVEY_URL_KEY);
showToast('所有自动化提交任务已完成!');
}
}
}
function checkAndContinueAutomation() {
const remaining = Number(localStorage.getItem(REMAINING_COUNT_KEY) || 0);
if (remaining > 0) {
showToast(`正在进行自动化提交,剩余 ${remaining} 份...`);
setTimeout(() => {
executeAutoFillAndSubmit();
}, 2000);
}
}
function createSurveyKey() {
return STORAGE_PREFIX + `${location.host}${location.pathname}`.replace(/[^a-zA-Z0-9_-]/g, '_');
}
function injectStyles() {
const style = document.createElement('style');
style.textContent = STYLE_TEXT;
document.head.appendChild(style);
}
function renderPanel() {
const panel = document.createElement('aside');
panel.id = PANEL_ID;
panel.innerHTML = `
`;
document.body.appendChild(panel);
bindEvents(panel);
}
function bindEvents(panel) {
panel.querySelector('#wjx-toggle-panel').addEventListener('click', () => {
panel.classList.toggle('is-collapsed');
const body = panel.querySelector('#wjx-panel-body');
const button = panel.querySelector('#wjx-toggle-panel');
const collapsed = panel.classList.contains('is-collapsed');
body.classList.toggle('wjx-hidden', collapsed);
button.textContent = collapsed ? '展开' : '收起';
});
panel.querySelector('#wjx-target-count').addEventListener('input', (event) => {
State.config.targetCount = Math.max(1, Number(event.target.value) || 1);
});
panel.querySelector('#wjx-randomize-all').addEventListener('click', () => {
randomizeAllQuestions();
});
panel.querySelector('#wjx-rescan').addEventListener('click', () => {
refreshQuestions({ silent: false, preserveInputs: true });
});
panel.querySelector('#wjx-start-automation').addEventListener('click', () => {
syncStateFromDom();
saveConfig();
startAutomation();
});
panel.addEventListener('click', (event) => {
const trigger = event.target.closest('[data-preset]');
if (!trigger) {
return;
}
applyPreset(trigger.closest('.wjx-card'), trigger.dataset.preset);
});
}
function refreshQuestions(options) {
const settings = Object.assign({ silent: false, preserveInputs: true }, options);
const previousQuestions = settings.preserveInputs ? collectCurrentQuestionsFromDom() : [];
const previousTargetCount = getCurrentTargetCount();
const savedConfig = loadConfig();
const scannedQuestions = scanQuestions();
State.config = mergeConfig(savedConfig, previousQuestions, previousTargetCount, scannedQuestions);
renderStats();
renderQuestionList();
const targetInput = document.getElementById('wjx-target-count');
if (targetInput) {
targetInput.value = String(State.config.targetCount);
}
if (shouldAutoRandomize(settings, previousQuestions, savedConfig, scannedQuestions)) {
randomizeAllQuestions({ silent: true });
}
if (!settings.silent) {
showToast(scannedQuestions.length ? `已重新识别 ${scannedQuestions.length} 道题。` : '当前页面没有识别到可配置题目。');
}
}
function shouldAutoRandomize(settings, previousQuestions, savedConfig, scannedQuestions) {
if (!scannedQuestions.length) {
return false;
}
if (settings.preserveInputs && previousQuestions.length) {
return false;
}
if (hasConfiguredQuestions(savedConfig && savedConfig.questions)) {
return false;
}
return true;
}
function hasConfiguredQuestions(list) {
if (!Array.isArray(list) || !list.length) {
return false;
}
return list.some((question) => {
if (question.type === 'text') {
return Array.isArray(question.content) && question.content.length > 0;
}
return Array.isArray(question.weights) && question.weights.some((weight) => Number(weight) > 1);
});
}
function mergeConfig(savedConfig, previousQuestions, previousTargetCount, scannedQuestions) {
const mergedQuestions = scannedQuestions.map((question) => {
const previous = findMatchingQuestion(previousQuestions, question) || findMatchingQuestion(savedConfig && savedConfig.questions, question);
if (!previous) {
return question;
}
const next = Object.assign({}, question);
if (question.type === 'text') {
next.content = Array.isArray(previous.content) && previous.content.length ? previous.content.slice() : question.content.slice();
} else if (Array.isArray(previous.weights) && previous.weights.length === question.weights.length) {
next.weights = previous.weights.slice();
}
return next;
});
return {
targetCount: Math.max(1, Number(previousTargetCount || (savedConfig && savedConfig.targetCount) || 1) || 1),
questions: mergedQuestions
};
}
function findMatchingQuestion(list, question) {
if (!Array.isArray(list)) {
return null;
}
return list.find((item) => String(item.id) === String(question.id) && item.type === question.type) || null;
}
function loadConfig() {
try {
return JSON.parse(localStorage.getItem(State.surveyKey) || 'null');
} catch (error) {
return null;
}
}
function saveConfig() {
localStorage.setItem(State.surveyKey, JSON.stringify({
targetCount: State.config.targetCount,
questions: State.config.questions
}));
}
function renderStats() {
document.getElementById('wjx-stat-total').textContent = String(State.config.questions.length);
document.getElementById('wjx-stat-types').textContent = String(new Set(State.config.questions.map((item) => item.type)).size);
}
function renderQuestionList() {
const container = document.getElementById('wjx-question-list');
if (!container) {
return;
}
if (!State.config.questions.length) {
container.innerHTML = `
未识别到题目
`;
return;
}
container.innerHTML = State.config.questions.map((question) => renderQuestionCard(question)).join('');
}
function renderQuestionCard(question) {
const title = escapeHtml(question.title || `题目 ${question.order}`);
const typeLabel = TYPE_LABELS[question.type] || TYPE_LABELS.unknown;
const meta = buildQuestionMeta(question);
const editor = question.type === 'text'
? `
`
: question.type === 'unknown'
? ''
: `
${question.optionLabels.map((label, index) => `
${escapeHtml(label)}
`).join('')}
`;
const tools = question.type === 'text'
? ``
: question.type === 'unknown'
? ''
: `
`;
return `
Q${question.order}. ${title}
${typeLabel}
${meta}
${editor}
${tools}
`;
}
function buildQuestionMeta(question) {
if (question.type === 'matrix') {
return `${question.rowCount || 0} 行 ${question.optionLabels.length} 列`;
}
if (question.type === 'text') {
return '填空题';
}
if (question.type === 'unknown') {
return '未知题型';
}
if (question.type === 'checkbox' && question.minSelections) {
return `${question.optionLabels.length} 个选项,最少选 ${question.minSelections} 项`;
}
return `${question.optionLabels.length} 个选项`;
}
function syncStateFromDom() {
State.config.targetCount = getCurrentTargetCount();
State.config.questions = collectCurrentQuestionsFromDom();
}
function getCurrentTargetCount() {
const input = document.getElementById('wjx-target-count');
return Math.max(1, Number(input && input.value) || 1);
}
function collectCurrentQuestionsFromDom() {
return Array.from(document.querySelectorAll(`#${PANEL_ID} .wjx-card`)).map((card, index) => {
const id = card.dataset.qid;
const type = card.dataset.type;
const original = State.config.questions.find((item) => String(item.id) === String(id) && item.type === type) || {};
const next = {
id,
order: original.order || index + 1,
title: original.title || '',
type,
optionLabels: Array.isArray(original.optionLabels) ? original.optionLabels.slice() : [],
rowCount: original.rowCount || 0,
minSelections: original.minSelections || 0,
maxSelections: original.maxSelections || 0
};
if (type === 'text') {
const area = card.querySelector('[data-role="content-editor"]');
next.content = area
? area.value.split('\n').map((line) => line.trim()).filter(Boolean)
: DEFAULT_TEXT_LIBRARY.slice();
} else if (type !== 'unknown') {
next.weights = Array.from(card.querySelectorAll('[data-role="weight-input"]')).map((input) => {
const value = Number(input.value);
return Number.isFinite(value) && value >= 0 ? value : 0;
});
}
return next;
});
}
function applyPreset(card, preset) {
if (!card) {
return;
}
if (preset === 'text-default') {
const area = card.querySelector('[data-role="content-editor"]');
if (area) {
area.value = DEFAULT_TEXT_LIBRARY.join('\n');
showToast('已填入默认词库。');
}
return;
}
const inputs = Array.from(card.querySelectorAll('[data-role="weight-input"]'));
if (!inputs.length) {
return;
}
let values = [];
if (preset === 'random') {
values = inputs.map(() => Math.floor(Math.random() * 100) + 1);
showToast('已生成随机比例。');
}
inputs.forEach((input, index) => {
input.value = String(values[index] || 0);
});
}
function randomizeAllQuestions(options) {
const settings = Object.assign({ silent: false }, options);
const cards = Array.from(document.querySelectorAll(`#${PANEL_ID} .wjx-card`));
cards.forEach((card) => {
const type = card.dataset.type;
if (type === 'text') {
const area = card.querySelector('[data-role="content-editor"]');
if (area) {
area.value = DEFAULT_TEXT_LIBRARY.join('\n');
}
return;
}
if (type === 'unknown') {
return;
}
card.querySelectorAll('[data-role="weight-input"]').forEach((input) => {
input.value = String(Math.floor(Math.random() * 100) + 1);
});
});
syncStateFromDom();
saveConfig();
if (!settings.silent) {
showToast('已随机全部题目。');
}
}
function scanQuestions() {
return findQuestionNodes().map((node, index) => buildQuestionConfig(node, index + 1)).filter(Boolean);
}
function findQuestionNodes() {
const selectors = [
'.field.ui-field-contain',
'.div_question',
'.div_table_radio_question',
'.ui-field-contain'
];
const nodes = Array.from(document.querySelectorAll(selectors.join(',')));
return nodes.filter((node, index, list) => {
if (!(node instanceof HTMLElement)) {
return false;
}
if (!extractTitle(node)) {
return false;
}
return !list.some((other, otherIndex) => otherIndex !== index && other.contains(node));
});
}
function buildQuestionConfig(node, order) {
const title = extractTitle(node);
if (!title) {
return null;
}
const type = detectType(node);
const base = {
id: extractQuestionId(node, order),
order,
title,
type,
optionLabels: [],
rowCount: 0,
minSelections: 0,
maxSelections: 0
};
if (type === 'text') {
return Object.assign(base, { content: DEFAULT_TEXT_LIBRARY.slice() });
}
if (type === 'unknown') {
return base;
}
const detail = extractDetail(node, type);
const limits = extractSelectionLimits(node, type, detail.optionLabels.length);
return Object.assign(base, {
optionLabels: detail.optionLabels,
rowCount: detail.rowCount || 0,
minSelections: limits.minSelections,
maxSelections: limits.maxSelections,
weights: Array.from({ length: detail.optionLabels.length }, () => 1)
});
}
function extractQuestionId(node, fallbackOrder) {
const raw = node.id || node.getAttribute('topic') || '';
const match = raw.match(/(\d+)/);
return match ? match[1] : String(fallbackOrder);
}
function extractTitle(node) {
const titleNode = node.querySelector('.div_title_question, .ui-controlgroup-label, .field-label, .legend, .title');
return cleanText(titleNode ? titleNode.textContent : '').replace(/^\d+\s*[\.、)]\s*/, '');
}
function detectType(node) {
if (node.querySelector('.div_table_radio_question, .div_table_clear_top')) {
return 'matrix';
}
const table = node.querySelector('table');
if (table && table.querySelector('input[type="radio"], input[type="checkbox"], .ui-radio, .ui-checkbox')) {
return 'matrix';
}
if (node.querySelector('.city-container, .divProvince, .divCity')) {
return 'location';
}
if (node.querySelector('.reorder-list, .ui-sortable')) {
return 'sorting';
}
if (node.querySelector('.scale-div, .rating-star, .onscore, .starlevel')) {
return 'rating';
}
if (node.querySelector('.ui-checkbox, .jqCheckbox, input[type="checkbox"]')) {
return 'checkbox';
}
if (node.querySelector('.ui-radio, .jqRadio, input[type="radio"]')) {
return 'radio';
}
if (node.querySelector('select')) {
return 'dropdown';
}
if (node.querySelector('textarea, input[type="text"], input[type="tel"], input[type="email"], input[type="number"]')) {
return 'text';
}
return 'unknown';
}
function extractSelectionLimits(node, type, optionCount) {
const rawMin = Number(node.getAttribute('minvalue') || 0);
const rawMax = Number(node.getAttribute('maxvalue') || 0);
let minSelections = Number.isFinite(rawMin) && rawMin > 0 ? rawMin : 0;
let maxSelections = Number.isFinite(rawMax) && rawMax > 0 ? rawMax : 0;
if (!minSelections && (type === 'checkbox' || type === 'radio') && node.getAttribute('req') === '1') {
minSelections = 1;
}
minSelections = Math.min(Math.max(minSelections, 0), optionCount || 0);
maxSelections = Math.min(Math.max(maxSelections, 0), optionCount || 0);
if (maxSelections && maxSelections < minSelections) {
maxSelections = minSelections;
}
return { minSelections, maxSelections };
}
function extractDetail(node, type) {
if (type === 'dropdown') {
const select = node.querySelector('select');
const optionLabels = Array.from(select ? select.options : [])
.map((item) => trimOptionPrefix(cleanText(item.textContent)))
.filter((text) => text && !/^请选择/.test(text));
return {
optionLabels: optionLabels.length ? optionLabels : ['选项1', '选项2'],
rowCount: 0
};
}
if (type === 'matrix') {
return extractMatrixDetail(node);
}
const optionLabels = extractChoiceLabels(node);
return {
optionLabels: optionLabels.length ? optionLabels : ['选项1', '选项2'],
rowCount: 0
};
}
function extractChoiceLabels(node) {
const selectors = [
'.ulradiocheck li',
'.ui-controlgroup .ui-radio',
'.ui-controlgroup .ui-checkbox',
'.label',
'.option-item',
'.wjx-options li'
];
const rawTexts = [];
selectors.forEach((selector) => {
node.querySelectorAll(selector).forEach((item) => {
const text = trimOptionPrefix(cleanText(item.textContent));
if (text) {
rawTexts.push(text);
}
});
});
return dedupeTexts(rawTexts).slice(0, 20);
}
function extractMatrixDetail(node) {
const table = node.querySelector('table');
if (!table) {
return { optionLabels: ['选项1', '选项2'], rowCount: 0 };
}
let headerCells = [];
const theadCells = table.querySelectorAll('thead tr:first-child th, thead tr:first-child td');
if (theadCells.length > 1) {
headerCells = Array.from(theadCells).slice(1);
} else {
const firstRow = table.querySelector('tbody tr, tr');
const bodyCells = firstRow ? firstRow.querySelectorAll('td, th') : [];
if (bodyCells.length > 1) {
headerCells = Array.from(bodyCells).slice(1);
}
}
const optionLabels = dedupeTexts(headerCells.map((cell) => trimOptionPrefix(cleanText(cell.textContent)))).filter(Boolean);
const rowCount = table.querySelectorAll('tbody tr').length || Math.max(0, table.querySelectorAll('tr').length - 1);
return {
optionLabels: optionLabels.length ? optionLabels : ['选项1', '选项2'],
rowCount
};
}
function dedupeTexts(list) {
const result = [];
const seen = new Set();
list.forEach((item) => {
const normalized = cleanText(item);
if (!normalized || seen.has(normalized)) {
return;
}
seen.add(normalized);
result.push(normalized);
});
return result;
}
function trimOptionPrefix(text) {
return String(text || '').replace(/^[A-ZA-Za-za-z0-90-9]+[\.\、\)]\s*/, '').trim();
}
function cleanText(text) {
return String(text || '').replace(/\s+/g, ' ').trim();
}
function escapeHtml(value) {
return String(value || '')
.replace(/&/g, '&')
.replace(//g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
let toastTimer = null;
function showToast(message, options) {
const settings = Object.assign({ html: false, duration: 2200, actionable: false, href: '' }, options);
let toast = document.querySelector('.wjx-toast');
if (!toast) {
toast = document.createElement('div');
toast.className = 'wjx-toast';
document.body.appendChild(toast);
}
toast.classList.toggle('is-actionable', settings.actionable);
toast.onclick = null;
if (settings.html) {
toast.innerHTML = message;
} else {
toast.textContent = message;
}
if (settings.actionable && settings.href) {
toast.onclick = () => {
window.open(settings.href, '_blank', 'noopener,noreferrer');
};
}
toast.classList.add('is-visible');
clearTimeout(toastTimer);
toastTimer = setTimeout(() => {
toast.classList.remove('is-visible');
}, settings.duration);
}
function schedulePromoToast() {
try {
const key = `${PROMO_TOAST_KEY}_${location.host}${location.pathname}`;
if (sessionStorage.getItem(key)) {
return;
}
sessionStorage.setItem(key, '1');
setTimeout(() => {
showToast('脚本已生效!如果觉得好用,可以点击这里在 Greasy Fork 收藏一下支持作者吗?', {
html: true,
duration: 5200,
actionable: true,
href: GREASYFORK_URL
});
}, 900);
} catch (error) {}
}
function scheduleRescan() {
setTimeout(() => {
if (document.getElementById(PANEL_ID)) {
refreshQuestions({ silent: true, preserveInputs: true });
}
}, 1500);
}
function startAutomation() {
const count = State.config.targetCount;
if (count <= 0) {
showToast('请输入有效的设计份数。');
return;
}
localStorage.setItem(REMAINING_COUNT_KEY, String(count));
localStorage.setItem(SURVEY_URL_KEY, location.href);
executeAutoFillAndSubmit();
}
async function executeAutoFillAndSubmit() {
try {
const remaining = Number(localStorage.getItem(REMAINING_COUNT_KEY) || 0);
if (remaining <= 0) return;
await fillAllQuestions();
// Wait a bit to look more natural
await new Promise(resolve => setTimeout(resolve, 1500));
await submitSurvey();
} catch (error) {
console.error('Automation error:', error);
showToast(`提交出错: ${error.message || '未知错误'}`);
// If error, maybe stop automation to avoid infinite reloads
localStorage.removeItem(REMAINING_COUNT_KEY);
}
}
async function fillAllQuestions() {
const questionNodes = findQuestionNodes();
for (const question of State.config.questions) {
const node = questionNodes.find(n => extractQuestionId(n, 0) === String(question.id));
if (node) {
await fillQuestion(question, node);
// Random delay between questions
await new Promise(resolve => setTimeout(resolve, 200 + Math.random() * 300));
}
}
}
async function fillQuestion(question, node) {
switch (question.type) {
case 'radio':
case 'dropdown':
case 'rating':
const index = pickWeightedIndex(question.weights);
clickOption(node, index, question.type);
break;
case 'checkbox':
const indices = pickMultipleIndices(question.weights, question.minSelections, question.maxSelections);
indices.forEach(idx => clickOption(node, idx, question.type));
break;
case 'text':
const text = pickRandomText(question.content);
fillText(node, text);
break;
case 'matrix':
fillMatrix(node, question.weights);
break;
case 'location':
await fillLocation(node);
break;
case 'sorting':
await fillSorting(node);
break;
}
}
async function fillLocation(node) {
const trigger = node.querySelector('textarea, input, .ui-select, .city-container');
if (trigger) {
trigger.click();
await new Promise(resolve => setTimeout(resolve, 1000));
const clickAndPick = async (selector) => {
const btn = document.querySelector(selector);
if (btn) {
btn.dispatchEvent(new MouseEvent('mousedown', { bubbles: true }));
await new Promise(r => setTimeout(r, 500));
const options = document.querySelectorAll('[id$=-results] > li:not(:first-child)');
if (options.length > 0) {
const randomOpt = options[Math.floor(Math.random() * options.length)];
randomOpt.dispatchEvent(new MouseEvent('mouseup', { bubbles: true }));
await new Promise(r => setTimeout(r, 500));
return true;
}
}
return false;
};
const hasProvince = await clickAndPick('.divProvince, [id^=select2-province]');
if (hasProvince) {
await clickAndPick('.divCity, [id^=select2-city]');
await clickAndPick('.divArea, [id^=select2-area]');
const saveBtn = document.querySelector('.layer_save_btn a, .ui-dialog .save_btn');
if (saveBtn) saveBtn.click();
}
}
}
async function fillSorting(node) {
const items = Array.from(node.querySelectorAll('li, .ui-sortable-handle, .reorder-item'));
if (items.length > 0) {
const shuffled = items.sort(() => Math.random() - 0.5);
for (const item of shuffled) {
item.click();
await new Promise(r => setTimeout(r, 300));
}
}
}
function pickWeightedIndex(weights) {
const total = weights.reduce((a, b) => a + b, 0);
if (total <= 0) return Math.floor(Math.random() * weights.length);
let random = Math.random() * total;
for (let i = 0; i < weights.length; i++) {
if (random < weights[i]) return i;
random -= weights[i];
}
return weights.length - 1;
}
function pickMultipleIndices(weights, minSelections, maxSelections) {
const totalOptions = weights.length;
if (!totalOptions) {
return [];
}
const minCount = Math.max(0, Math.min(Number(minSelections) || 0, totalOptions));
const defaultMax = Math.min(totalOptions, Math.max(minCount || 1, Math.min(3, totalOptions)));
const maxCount = Math.max(minCount, Math.min(Number(maxSelections) || defaultMax, totalOptions));
const count = minCount >= maxCount ? Math.max(1, minCount || 1) : randomInt(minCount || 1, maxCount);
const pool = weights.map((weight, index) => ({
index,
weight: Math.max(1, Number(weight) || 1)
}));
const results = [];
while (results.length < count && pool.length > 0) {
const total = pool.reduce((sum, item) => sum + item.weight, 0);
let random = Math.random() * total;
let selectedIndex = 0;
for (let i = 0; i < pool.length; i++) {
random -= pool[i].weight;
if (random <= 0) {
selectedIndex = i;
break;
}
}
results.push(pool[selectedIndex].index);
pool.splice(selectedIndex, 1);
}
return results;
}
function pickRandomText(texts) {
if (!texts || texts.length === 0) return DEFAULT_TEXT_LIBRARY[Math.floor(Math.random() * DEFAULT_TEXT_LIBRARY.length)];
return texts[Math.floor(Math.random() * texts.length)];
}
function randomInt(min, max) {
return min + Math.floor(Math.random() * (max - min + 1));
}
function getChoiceElements(node, type) {
const controlGroup = node.querySelector('.ui-controlgroup');
if (type === 'checkbox' && controlGroup) {
const directOptions = Array.from(controlGroup.children).filter((item) => item.matches('.ui-checkbox, li, .option-item'));
if (directOptions.length) {
return directOptions;
}
}
if ((type === 'radio' || type === 'rating') && controlGroup) {
const directOptions = Array.from(controlGroup.children).filter((item) => item.matches('.ui-radio, li, .option-item'));
if (directOptions.length) {
return directOptions;
}
}
if (type === 'checkbox') {
const options = Array.from(node.querySelectorAll('.ui-controlgroup .ui-checkbox, .ulradiocheck > li, .option-item'));
return options.length ? options : Array.from(node.querySelectorAll('input[type="checkbox"]'));
}
if (type === 'radio' || type === 'rating') {
const options = Array.from(node.querySelectorAll('.ui-controlgroup .ui-radio, .ulradiocheck > li, .scale-div li, .rating-star, .onscore, .starlevel, .option-item'));
return options.length ? options : Array.from(node.querySelectorAll('input[type="radio"]'));
}
return [];
}
function isChoiceSelected(option, type) {
if (!option) {
return false;
}
const inputSelector = type === 'checkbox' ? 'input[type="checkbox"]' : 'input[type="radio"]';
const input = option.matches(inputSelector) ? option : option.querySelector(inputSelector);
if (input) {
return !!input.checked;
}
return option.classList.contains('checked') ||
option.classList.contains('active') ||
option.classList.contains('on') ||
option.querySelector('.jqchecked, .checked, .active, a.checked, a.jqchecked') !== null;
}
function clickChoiceElement(option) {
if (!option) {
return;
}
const targets = [
option,
option.querySelector('.label'),
option.querySelector('.jqcheck, .jqradio, a.jqcheck, a.jqradio'),
option.querySelector('label'),
option.querySelector('input')
].filter(Boolean);
for (const target of targets) {
['mousedown', 'mouseup'].forEach((eventName) => {
target.dispatchEvent(new MouseEvent(eventName, { bubbles: true, cancelable: true }));
});
if (typeof target.click === 'function') {
target.click();
}
if (isChoiceSelected(option, 'checkbox') || isChoiceSelected(option, 'radio')) {
break;
}
}
const otherInput = option.querySelector('input.OtherText, .OtherText');
if (otherInput && !otherInput.value) {
otherInput.value = '其他';
otherInput.dispatchEvent(new Event('input', { bubbles: true }));
otherInput.dispatchEvent(new Event('change', { bubbles: true }));
}
}
function clickOption(node, index, type) {
if (type === 'dropdown') {
const select = node.querySelector('select');
if (select && select.options.length > index) {
let realIndex = index;
// If the first option is "Please select", we skip it
if (select.options[0].text.includes('请选择')) {
realIndex += 1;
}
if (select.options[realIndex]) {
select.selectedIndex = realIndex;
select.dispatchEvent(new Event('change', { bubbles: true }));
}
}
return;
}
const options = getChoiceElements(node, type);
if (options[index]) {
const option = options[index];
clickChoiceElement(option);
if (!isChoiceSelected(option, type)) {
const fallback = option.querySelector(type === 'checkbox' ? 'input[type="checkbox"]' : 'input[type="radio"]');
if (fallback && typeof fallback.click === 'function') {
fallback.click();
}
}
}
}
function fillText(node, text) {
const input = node.querySelector('textarea, input[type="text"], input[type="tel"], input[type="email"], input[type="number"]');
if (input) {
input.value = text;
input.dispatchEvent(new Event('input', { bubbles: true }));
input.dispatchEvent(new Event('change', { bubbles: true }));
}
}
function fillMatrix(node, weights) {
const rows = node.querySelectorAll('tr[rowindex]');
rows.forEach(row => {
const index = pickWeightedIndex(weights);
const inputs = row.querySelectorAll('input, .ui-radio, .ui-checkbox, td:not(:first-child)');
if (inputs[index]) {
inputs[index].click();
}
});
}
async function submitSurvey() {
const submitBtn = document.querySelector('#ctlNext, #submit_button, .submitbutton, .btn-submit, a.button.mainBgColor');
if (submitBtn) {
submitBtn.click();
// Wait for potential verification or next page
await new Promise(resolve => setTimeout(resolve, 3000));
await handleVerification();
}
}
async function handleVerification() {
const rectMask = document.querySelector('#rectMask');
if (rectMask) {
rectMask.click();
await new Promise(resolve => setTimeout(resolve, 2000));
await simulateSlider();
}
}
async function simulateSlider() {
const slider = document.querySelector('#nc_1__scale_text > span') || document.querySelector('.nc_iconfont.btn_slide');
if (slider) {
const rect = slider.getBoundingClientRect();
const startX = rect.left + rect.width / 2;
const startY = rect.top + rect.height / 2;
const mouseDown = new MouseEvent('mousedown', {
bubbles: true,
cancelable: true,
clientX: startX,
clientY: startY
});
slider.dispatchEvent(mouseDown);
const steps = 15;
const totalWidth = 300;
for (let i = 0; i <= steps; i++) {
await new Promise(r => setTimeout(r, 50 + Math.random() * 100));
const mouseMove = new MouseEvent('mousemove', {
bubbles: true,
cancelable: true,
clientX: startX + (totalWidth / steps) * i + (Math.random() * 6 - 3),
clientY: startY + (Math.random() * 6 - 3)
});
window.dispatchEvent(mouseMove);
}
const mouseUp = new MouseEvent('mouseup', {
bubbles: true,
cancelable: true
});
window.dispatchEvent(mouseUp);
}
}
init();
})();