// ==UserScript== // @name 问卷星自动答题助手 // @namespace https://wjx.panel.local // @version 0.0.3 // @description 一款功能强大的问卷星辅助工具。支持全题型识别(单选、多选、填空、矩阵、量表、滑块、地区、排序),通过可视化面板自定义各选项权重或次数及设计份数,支持比例/数量模式切换。支持一键破解复制限制,全自动模拟填写与批量提交,内置滑块验证模拟及本地存储清理功能。 // @author x7R2p9,yangwenren // @match https://*.wjx.top/* // @match https://*.wjx.cn/* // @match https://*.wjx.com/* // @grant none // @run-at document-end // @license MIT // @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 TOTAL_COUNT_KEY = 'WJX_TOTAL_COUNT'; const SURVEY_URL_KEY = 'WJX_SURVEY_URL'; const PANEL_COLLAPSED_KEY = 'WJX_PANEL_COLLAPSED_BEFORE_AUTOMATION'; const QUOTA_STATE_KEY = 'WJX_COUNT_QUOTA_STATE'; const GREASYFORK_URL = 'https://scriptcat.org/zh-CN/script-show-page/6321'; const PROMO_TOAST_KEY = 'WJX_GREASYFORK_PROMO_SHOWN'; const DEFAULT_TEXT_LIBRARY = ['很好', '满意', '支持', '体验不错']; const TYPE_LABELS = { radio: '单选题', checkbox: '多选题', dropdown: '下拉题', text: '填空题', rating: '量表题', matrix: '矩阵题', slide: '滑块题', location: '地区题', sorting: '排序题', unknown: '未知题型' }; const State = { surveyKey: '', config: { targetCount: 1, distributionMode: 'ratio', 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-automation-hidden { display: none !important; } .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-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-mode-switch { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; } .wjx-mode-btn { min-height: 36px; border-radius: 6px; padding: 0 10px; font-size: 12px; font-weight: 700; cursor: pointer; color: var(--wjx-text); background: #ffffff; border: 1px solid #c9dbff; } .wjx-mode-btn.is-active { color: #ffffff; background: var(--wjx-primary); border-color: var(--wjx-primary); } .wjx-mode-hint { margin: 0 0 10px; font-size: 11px; line-height: 1.5; color: var(--wjx-subtle); } .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-progress-modal { position: fixed; top: 16px; left: 50%; z-index: 2147483647; min-width: 240px; padding: 14px 18px; border-radius: 10px; border: 1px solid #b9d2ff; background: rgba(255, 255, 255, 0.98); color: var(--wjx-text); box-shadow: 0 12px 32px rgba(0, 82, 217, 0.18); font-family: "PingFang SC", "Microsoft YaHei", "Noto Sans SC", sans-serif; text-align: center; opacity: 0; transform: translate(-50%, -8px); transition: opacity 0.2s ease, transform 0.2s ease; pointer-events: none; } .wjx-progress-modal.is-visible { opacity: 1; transform: translate(-50%, 0); pointer-events: auto; } .wjx-progress-title { margin: 0 0 8px; font-size: 13px; font-weight: 700; color: var(--wjx-primary-deep); } .wjx-progress-count { margin: 0 0 10px; font-size: 16px; font-weight: 800; color: var(--wjx-text); } .wjx-progress-bar-wrap { height: 8px; margin-bottom: 8px; border-radius: 999px; background: #e8f1ff; overflow: hidden; } .wjx-progress-bar { height: 100%; width: 0; border-radius: 999px; background: linear-gradient(90deg, var(--wjx-primary), #3d8bff); transition: width 0.25s ease; } .wjx-progress-percent { margin: 0; font-size: 12px; font-weight: 700; color: var(--wjx-subtle); } .wjx-progress-stop { margin-top: 12px; min-height: 34px; padding: 0 16px; border-radius: 6px; border: 1px solid #ffb4b4; background: #fff5f5; color: #d4380d; font-size: 13px; font-weight: 700; cursor: pointer; } .wjx-progress-stop:hover { background: #ffece8; } .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)); } } `; 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 }); updateAllRelationVisibility(); 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) { ensureTotalCountForAutomation(); hidePanelForAutomation(); showAutomationProgress(); const url = localStorage.getItem(SURVEY_URL_KEY); if (url) { clearTimeout(automationRedirectTimer); automationRedirectTimer = setTimeout(() => { automationRedirectTimer = null; if (!isAutomationActive()) { return; } location.href = url; }, 2000); } } else { endAutomationSession('所有自动化提交任务已完成!'); } updateAutomationProgress(); } } function checkAndContinueAutomation() { const remaining = Number(localStorage.getItem(REMAINING_COUNT_KEY) || 0); if (remaining > 0) { ensureTotalCountForAutomation(); ensureQuotaState(); beginAutomationSession(); 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 = `

问卷星配置面板

识别题目 0
题型种类 0

全局设置

比例模式:各选项数值为权重比例。

题目配置

`; 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-mode-ratio').addEventListener('click', () => { setDistributionMode('ratio'); }); panel.querySelector('#wjx-mode-count').addEventListener('click', () => { setDistributionMode('count'); }); 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); } updateDistributionModeUI(); if (shouldAutoRandomize(settings, previousQuestions, savedConfig, scannedQuestions)) { randomizeAllQuestions({ silent: true }); } updateAllRelationVisibility(); if (!settings.silent) { const relationCount = scannedQuestions.filter((item) => Array.isArray(item.relations) && item.relations.length).length; if (scannedQuestions.length) { const relationTip = relationCount ? `,其中 ${relationCount} 道含题目关联` : ''; showToast(`已重新识别 ${scannedQuestions.length} 道题${relationTip}。`); } else { showToast('当前页面没有识别到可配置题目。'); } } } function shouldAutoRandomize(settings, previousQuestions, savedConfig, scannedQuestions) { if (!scannedQuestions.length) { return false; } if (settings.preserveInputs && previousQuestions.length) { return false; } if (hasConfiguredQuestions(savedConfig && savedConfig.questions, savedConfig && savedConfig.distributionMode)) { return false; } return true; } function hasConfiguredQuestions(list, distributionMode) { if (!Array.isArray(list) || !list.length) { return false; } const countMode = isCountModeFromConfig({ distributionMode }); return list.some((question) => { if (question.type === 'text') { return Array.isArray(question.content) && question.content.length > 0; } if (question.type === 'slide') { return clampScore(question.minScore, 1) !== 1 || clampScore(question.maxScore, 100) !== 100; } if (countMode) { return Array.isArray(question.weights) && question.weights.some((weight) => Number(weight) > 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 (question.type === 'slide') { next.minScore = clampScore(previous.minScore, question.minScore); next.maxScore = clampScore(previous.maxScore, question.maxScore); if (next.maxScore < next.minScore) { next.maxScore = next.minScore; } } else if (Array.isArray(previous.weights) && previous.weights.length === question.weights.length) { next.weights = previous.weights.slice(); } return next; }); const mode = normalizeDistributionMode( (savedConfig && savedConfig.distributionMode) || State.config.distributionMode ); return { targetCount: Math.max(1, Number(previousTargetCount || (savedConfig && savedConfig.targetCount) || 1) || 1), distributionMode: mode, 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, distributionMode: normalizeDistributionMode(State.config.distributionMode), questions: State.config.questions })); } function normalizeDistributionMode(mode) { return mode === 'count' ? 'count' : 'ratio'; } function isCountModeFromConfig(config) { return normalizeDistributionMode(config && config.distributionMode) === 'count'; } function isCountMode() { return normalizeDistributionMode(State.config.distributionMode) === 'count'; } function supportsCountDistribution(type) { return ['radio', 'dropdown', 'rating', 'checkbox', 'matrix'].includes(type); } function setDistributionMode(mode) { syncStateFromDom(); State.config.distributionMode = normalizeDistributionMode(mode); saveConfig(); updateDistributionModeUI(); renderQuestionList(); showToast(mode === 'count' ? '已切换为数量模式' : '已切换为比例模式'); } function updateDistributionModeUI() { const mode = normalizeDistributionMode(State.config.distributionMode); State.config.distributionMode = mode; const ratioBtn = document.getElementById('wjx-mode-ratio'); const countBtn = document.getElementById('wjx-mode-count'); const hint = document.getElementById('wjx-mode-hint'); if (ratioBtn) { ratioBtn.classList.toggle('is-active', mode === 'ratio'); } if (countBtn) { countBtn.classList.toggle('is-active', mode === 'count'); } if (hint) { hint.textContent = mode === 'count' ? '数量模式:各选项数值为该选项被选中的次数。无关联题目次数合计应等于设计份数;有关联题目次数合计应等于其父题对应选项的次数。' : '比例模式:各选项数值为权重比例。'; } } function getWeightInputSuffix() { return isCountMode() ? '次数' : '比例'; } function getRandomPresetLabel() { return isCountMode() ? '均衡次数' : '随机比例'; } function distributeCountsEvenly(optionCount, total) { const count = Math.max(1, optionCount || 1); const target = Math.max(0, Math.round(Number(total) || 0)); const base = Math.floor(target / count); const remainder = target % count; return Array.from({ length: count }, (_, index) => base + (index < remainder ? 1 : 0)); } function buildRandomWeightValues(optionCount, question) { if (isCountMode()) { const targetQuestion = question || { relations: [] }; return distributeCountsEvenly(optionCount, getExpectedCountTotalForQuestion(targetQuestion)); } return Array.from({ length: optionCount }, () => Math.floor(Math.random() * 100) + 1); } function findConfigQuestionByTopicId(topicId) { return State.config.questions.find((item) => String(item.id) === String(topicId)) || null; } function getRelationOptionCount(relation) { const parent = findConfigQuestionByTopicId(relation.topicId); if (!parent || !Array.isArray(parent.weights)) { return null; } const optionIndex = Number(relation.optionIndex) - 1; if (!Number.isFinite(optionIndex) || optionIndex < 0 || optionIndex >= parent.weights.length) { return null; } return Math.max(0, Math.round(Number(parent.weights[optionIndex]) || 0)); } function getExpectedCountTotalForQuestion(question) { const total = State.config.targetCount; if (!Array.isArray(question.relations) || !question.relations.length) { return total; } const relationCounts = question.relations .map((relation) => getRelationOptionCount(relation)) .filter((value) => value !== null); if (!relationCounts.length) { return total; } return Math.min(...relationCounts); } function formatCountModeExpectedHint(question) { const expected = getExpectedCountTotalForQuestion(question); const total = State.config.targetCount; if (question.type === 'checkbox') { if (Array.isArray(question.relations) && question.relations.length) { return `${question.optionLabels.length} 个选项,数量为各选项在关联可见的 ${expected} 份答卷中的选中次数`; } return `${question.optionLabels.length} 个选项,数量为各选项在 ${total} 份答卷中的选中次数`; } if (Array.isArray(question.relations) && question.relations.length && expected !== total) { return `${question.optionLabels.length} 个选项,次数合计应为 ${expected}(关联 ${formatRelationText(question.relations)})`; } return `${question.optionLabels.length} 个选项,次数合计应为 ${expected}`; } function validateCountModeBeforeStart() { if (!isCountMode()) { return true; } const total = State.config.targetCount; const issues = []; State.config.questions.forEach((question) => { if (!supportsCountDistribution(question.type)) { return; } const weights = Array.isArray(question.weights) ? question.weights : []; const sum = weights.reduce((acc, value) => acc + Math.max(0, Math.round(Number(value) || 0)), 0); if (question.type === 'checkbox') { return; } const expected = getExpectedCountTotalForQuestion(question); if (sum !== expected) { const relationTip = Array.isArray(question.relations) && question.relations.length ? `(关联父题选项次数为 ${expected})` : `(设计份数为 ${total})`; issues.push(`Q${question.order} 选项次数合计为 ${sum},应为 ${expected}${relationTip}`); } }); if (!issues.length) { return true; } showToast(issues[0] + (issues.length > 1 ? ' 等' : '')); return false; } function loadQuotaState() { try { const raw = localStorage.getItem(QUOTA_STATE_KEY); return raw ? JSON.parse(raw) : null; } catch (error) { return null; } } function saveQuotaState(state) { localStorage.setItem(QUOTA_STATE_KEY, JSON.stringify(state)); } function clearQuotaState() { localStorage.removeItem(QUOTA_STATE_KEY); } function initQuotaState() { const quotas = {}; State.config.questions.forEach((question) => { if (!supportsCountDistribution(question.type)) { return; } const weights = (question.weights || []).map((value) => Math.max(0, Math.round(Number(value) || 0))); if (question.type === 'matrix') { const rowCount = Math.max(1, Number(question.rowCount) || 1); quotas[String(question.id)] = { type: 'matrix', rows: Array.from({ length: rowCount }, () => weights.slice()) }; return; } quotas[String(question.id)] = { type: 'flat', values: weights.slice() }; }); saveQuotaState({ surveyKey: State.surveyKey, quotas }); } function ensureQuotaState() { if (!isCountMode()) { clearQuotaState(); return; } const existing = loadQuotaState(); if (existing && existing.surveyKey === State.surveyKey && existing.quotas) { return; } initQuotaState(); } function getQuotaEntry(questionId) { const state = loadQuotaState(); if (!state || state.surveyKey !== State.surveyKey || !state.quotas) { return null; } return state.quotas[String(questionId)] || null; } function getFlatQuotas(questionId) { const entry = getQuotaEntry(questionId); if (!entry || entry.type !== 'flat' || !Array.isArray(entry.values)) { return null; } return entry.values; } function getMatrixRowQuotas(questionId, rowIndex) { const entry = getQuotaEntry(questionId); if (!entry || entry.type !== 'matrix' || !Array.isArray(entry.rows)) { return null; } return entry.rows[rowIndex] || null; } function pickQuotaIndexFromValues(quotas) { const available = quotas .map((quota, index) => ({ index, quota: Math.max(0, Number(quota) || 0) })) .filter((item) => item.quota > 0); if (!available.length) { return Math.floor(Math.random() * quotas.length); } const pick = available[Math.floor(Math.random() * available.length)]; return pick.index; } function consumeFlatQuota(questionId, optionIndex) { const state = loadQuotaState(); const entry = state && state.quotas && state.quotas[String(questionId)]; if (!state || !entry || entry.type !== 'flat' || !Array.isArray(entry.values)) { return; } if (entry.values[optionIndex] > 0) { entry.values[optionIndex] -= 1; saveQuotaState(state); } } function consumeMatrixQuota(questionId, rowIndex, optionIndex) { const state = loadQuotaState(); const entry = getQuotaEntry(questionId); if (!state || !entry || entry.type !== 'matrix' || !entry.rows[rowIndex]) { return; } if (entry.rows[rowIndex][optionIndex] > 0) { entry.rows[rowIndex][optionIndex] -= 1; saveQuotaState(state); } } function pickOptionIndexForQuestion(question) { if (!isCountMode() || !supportsCountDistribution(question.type)) { return pickWeightedIndex(question.weights); } const quotas = getFlatQuotas(question.id); if (!quotas) { return pickWeightedIndex(question.weights); } return pickQuotaIndexFromValues(quotas); } function pickCheckboxIndicesForQuestion(question) { if (!isCountMode()) { return pickMultipleIndices(question.weights, question.minSelections, question.maxSelections); } const quotas = getFlatQuotas(question.id); if (!quotas) { return pickMultipleIndices(question.weights, question.minSelections, question.maxSelections); } const totalOptions = quotas.length; const available = quotas .map((quota, index) => ({ index, quota: Math.max(0, Number(quota) || 0) })) .filter((item) => item.quota > 0); if (!available.length) { return pickMultipleIndices(question.weights, question.minSelections, question.maxSelections); } const minCount = Math.max(0, Math.min(Number(question.minSelections) || 0, totalOptions)); const defaultMax = Math.min(totalOptions, Math.max(minCount || 1, Math.min(3, totalOptions))); const maxCount = Math.max(minCount, Math.min(Number(question.maxSelections) || defaultMax, totalOptions)); const desiredCount = minCount >= maxCount ? Math.max(1, minCount || 1) : randomInt(Math.max(1, minCount || 1), maxCount); const count = Math.min(desiredCount, available.length); const pool = available.slice(); const results = []; while (results.length < count && pool.length > 0) { const pick = pool[Math.floor(Math.random() * pool.length)]; results.push(pick.index); pool.splice(pool.indexOf(pick), 1); } return results; } function pickMatrixRowIndex(question, rowIndex) { if (!isCountMode()) { return pickWeightedIndex(question.weights); } const quotas = getMatrixRowQuotas(question.id, rowIndex); if (!quotas) { return pickWeightedIndex(question.weights); } return pickQuotaIndexFromValues(quotas); } 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 === 'slide' ? `
最小分值
最大分值
` : question.type === 'unknown' ? '' : `
${question.optionLabels.map((label, index) => `
${escapeHtml(label)}(${getWeightInputSuffix()})
`).join('')}
`; const tools = question.type === 'text' ? `
` : question.type === 'slide' ? `
` : question.type === 'unknown' ? '' : `
`; return `
Q${question.order}. ${title}
${typeLabel}
${meta}
${editor} ${tools}
`; } function buildQuestionMeta(question) { const relationText = formatRelationText(question.relations); if (relationText) { return `关联:${relationText}`; } if (question.type === 'matrix') { return `${question.rowCount || 0} 行 ${question.optionLabels.length} 列`; } if (question.type === 'text') { return '填空题'; } if (question.type === 'slide') { return `分值范围 ${clampScore(question.minScore, 1)} - ${clampScore(question.maxScore, 100)}`; } if (question.type === 'unknown') { return '未知题型'; } if (question.type === 'checkbox' && question.minSelections) { return `${question.optionLabels.length} 个选项,最少选 ${question.minSelections} 项`; } if (isCountMode() && supportsCountDistribution(question.type)) { return formatCountModeExpectedHint(question); } 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() : [], relations: Array.isArray(original.relations) ? original.relations.map((relation) => Object.assign({}, relation)) : [], rowCount: original.rowCount || 0, minSelections: original.minSelections || 0, maxSelections: original.maxSelections || 0, minScore: clampScore(original.minScore, 1), maxScore: clampScore(original.maxScore, 100) }; 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 === 'slide') { next.minScore = clampScore(card.querySelector('[data-role="slide-min"]') && card.querySelector('[data-role="slide-min"]').value, original.minScore || 1); next.maxScore = clampScore(card.querySelector('[data-role="slide-max"]') && card.querySelector('[data-role="slide-max"]').value, original.maxScore || 100); if (next.maxScore < next.minScore) { next.maxScore = next.minScore; } } 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; } if (preset === 'slide-default') { const minInput = card.querySelector('[data-role="slide-min"]'); const maxInput = card.querySelector('[data-role="slide-max"]'); if (minInput && maxInput) { const minScore = randomInt(20, 70); const maxScore = randomInt(minScore, Math.min(100, minScore + 30)); minInput.value = String(minScore); maxInput.value = String(maxScore); showToast('已生成随机分值区间。'); } return; } const inputs = Array.from(card.querySelectorAll('[data-role="weight-input"]')); if (!inputs.length) { return; } const original = State.config.questions.find((item) => { return String(item.id) === String(card.dataset.qid) && item.type === card.dataset.type; }) || { relations: [] }; const values = buildRandomWeightValues(inputs.length, original); inputs.forEach((input, index) => { input.value = String(values[index] || 0); }); showToast(isCountMode() ? '已按关联次数均衡分配。' : '已生成随机比例。'); } 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 === 'slide') { const minInput = card.querySelector('[data-role="slide-min"]'); const maxInput = card.querySelector('[data-role="slide-max"]'); if (minInput && maxInput) { const minScore = randomInt(20, 70); const maxScore = randomInt(minScore, Math.min(100, minScore + 30)); minInput.value = String(minScore); maxInput.value = String(maxScore); } return; } if (type === 'unknown') { return; } const original = State.config.questions.find((item) => { return String(item.id) === String(card.dataset.qid) && item.type === card.dataset.type; }) || { relations: [] }; const values = buildRandomWeightValues(card.querySelectorAll('[data-role="weight-input"]').length, original); card.querySelectorAll('[data-role="weight-input"]').forEach((input, index) => { input.value = String(values[index] || 0); }); }); syncStateFromDom(); saveConfig(); if (!settings.silent) { showToast(isCountMode() ? '已按各题关联次数均衡分配。' : '已随机全部题目。'); } } 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 parseRelations(node) { const raw = String(node.getAttribute('relation') || '').trim(); if (!raw) { return []; } return raw.split(';').map((part) => { const pieces = part.split(',').map((item) => item.trim()).filter(Boolean); if (pieces.length < 2) { return null; } const topicId = pieces[0].replace(/\D/g, '') || pieces[0]; const optionIndex = Number(pieces[1]); if (!topicId || !Number.isFinite(optionIndex) || optionIndex < 1) { return null; } return { topicId: String(topicId), optionIndex: Math.floor(optionIndex) }; }).filter(Boolean); } function findQuestionNodeById(topicId) { return findQuestionNodes().find((node) => String(extractQuestionId(node, 0)) === String(topicId)) || null; } function getRelationOptionLabel(topicId, optionIndex) { const parentNode = findQuestionNodeById(topicId); if (!parentNode) { return `选项${optionIndex}`; } const parentType = detectType(parentNode); const detail = extractDetail(parentNode, parentType); return detail.optionLabels[optionIndex - 1] || `选项${optionIndex}`; } function formatRelationText(relations) { if (!Array.isArray(relations) || !relations.length) { return ''; } return relations.map((relation) => { const label = getRelationOptionLabel(relation.topicId, relation.optionIndex); return `第${relation.topicId}题·${label}`; }).join(';'); } function isRelationSatisfied(relations) { if (!Array.isArray(relations) || !relations.length) { return true; } return relations.every((relation) => isOptionSelectedByIndex(relation.topicId, relation.optionIndex)); } function isOptionSelectedByIndex(topicId, optionIndex) { const parentNode = findQuestionNodeById(topicId); if (!parentNode) { return false; } return isOptionSelected(parentNode, optionIndex); } function isOptionSelected(node, optionIndex) { const type = detectType(node); if (type === 'dropdown') { const select = node.querySelector('select'); if (!select || select.selectedIndex < 0) { return false; } const selected = select.options[select.selectedIndex]; if (!selected) { return false; } if (Number(selected.value) === optionIndex) { return true; } let realIndex = select.selectedIndex; if (select.options[0] && /请选择/.test(cleanText(select.options[0].textContent))) { realIndex = select.selectedIndex; } return realIndex === optionIndex; } const valueInput = node.querySelector(`input[type="radio"][value="${optionIndex}"], input[type="checkbox"][value="${optionIndex}"]`); if (valueInput) { return !!valueInput.checked; } const choiceType = type === 'checkbox' ? 'checkbox' : 'radio'; const options = getChoiceElements(node, type === 'checkbox' ? 'checkbox' : (type === 'rating' ? 'rating' : 'radio')); const option = options[optionIndex - 1]; return !!(option && isChoiceSelected(option, choiceType)); } function updateAllRelationVisibility() { findQuestionNodes().forEach((node) => { const relations = parseRelations(node); if (!relations.length) { return; } node.style.display = isRelationSatisfied(relations) ? '' : 'none'; }); } 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, rawType: String(node.getAttribute('type') || ''), relations: parseRelations(node), optionLabels: [], rowCount: 0, minSelections: 0, maxSelections: 0, minScore: 1, maxScore: 100 }; if (type === 'text') { return Object.assign(base, { content: DEFAULT_TEXT_LIBRARY.slice() }); } if (type === 'slide') { return Object.assign(base, { minScore: 1, maxScore: 100 }); } 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) { const wjxType = String(node.getAttribute('type') || ''); if (wjxType === '6') { return 'matrix'; } if (wjxType === '5') { return 'rating'; } if (wjxType === '4') { return 'checkbox'; } if (wjxType === '3') { return 'radio'; } if (wjxType === '7') { return 'dropdown'; } if (wjxType === '11') { return 'sorting'; } if (wjxType === '1' || wjxType === '2') { return 'text'; } if (wjxType === '8') { return 'slide'; } 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, a[dval], .rate-off, .rate-on')) { 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"]:not([style*="display:none"]), 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 clampScore(value, fallback) { const score = Number(value); if (!Number.isFinite(score)) { return Number(fallback); } return Math.max(0, Math.min(100, Math.round(score))); } function escapeHtml(value) { return String(value || '') .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } let progressStyleInjected = false; let automationRedirectTimer = null; function injectProgressStyles() { if (progressStyleInjected || document.querySelector('style[data-wjx-progress]')) { progressStyleInjected = true; return; } const style = document.createElement('style'); style.setAttribute('data-wjx-progress', '1'); style.textContent = ` :root { --wjx-primary: #0064ff; --wjx-primary-deep: #0052d9; --wjx-text: #1f2d3d; --wjx-subtle: #5f6f82; } .wjx-progress-modal { position: fixed; top: 16px; left: 50%; z-index: 2147483647; min-width: 240px; padding: 14px 18px; border-radius: 10px; border: 1px solid #b9d2ff; background: rgba(255, 255, 255, 0.98); color: #1f2d3d; box-shadow: 0 12px 32px rgba(0, 82, 217, 0.18); font-family: "PingFang SC", "Microsoft YaHei", "Noto Sans SC", sans-serif; text-align: center; opacity: 0; transform: translate(-50%, -8px); transition: opacity 0.2s ease, transform 0.2s ease; pointer-events: none; } .wjx-progress-modal.is-visible { opacity: 1; transform: translate(-50%, 0); pointer-events: auto; } .wjx-progress-title { margin: 0 0 8px; font-size: 13px; font-weight: 700; color: #0052d9; } .wjx-progress-count { margin: 0 0 10px; font-size: 16px; font-weight: 800; } .wjx-progress-bar-wrap { height: 8px; margin-bottom: 8px; border-radius: 999px; background: #e8f1ff; overflow: hidden; } .wjx-progress-bar { height: 100%; width: 0; border-radius: 999px; background: linear-gradient(90deg, #0064ff, #3d8bff); transition: width 0.25s ease; } .wjx-progress-percent { margin: 0; font-size: 12px; font-weight: 700; color: #5f6f82; } .wjx-progress-stop { margin-top: 12px; min-height: 34px; padding: 0 16px; border-radius: 6px; border: 1px solid #ffb4b4; background: #fff5f5; color: #d4380d; font-size: 13px; font-weight: 700; cursor: pointer; } .wjx-progress-stop:hover { background: #ffece8; } `; document.head.appendChild(style); progressStyleInjected = true; } function hidePanelForAutomation() { const panel = document.getElementById(PANEL_ID); if (!panel) { return; } try { sessionStorage.setItem( PANEL_COLLAPSED_KEY, panel.classList.contains('is-collapsed') ? '1' : '0' ); } catch (error) {} panel.classList.add('is-automation-hidden'); } function showPanelAfterAutomation() { const panel = document.getElementById(PANEL_ID); if (!panel) { return; } panel.classList.remove('is-automation-hidden'); let collapsed = false; try { const saved = sessionStorage.getItem(PANEL_COLLAPSED_KEY); collapsed = saved === '1'; sessionStorage.removeItem(PANEL_COLLAPSED_KEY); } catch (error) {} const body = panel.querySelector('#wjx-panel-body'); const button = panel.querySelector('#wjx-toggle-panel'); panel.classList.toggle('is-collapsed', collapsed); if (body) { body.classList.toggle('wjx-hidden', collapsed); } if (button) { button.textContent = collapsed ? '展开' : '收起'; } } function clearAutomationStorage() { localStorage.removeItem(REMAINING_COUNT_KEY); localStorage.removeItem(TOTAL_COUNT_KEY); localStorage.removeItem(SURVEY_URL_KEY); clearQuotaState(); } function endAutomationSession(message) { clearTimeout(automationRedirectTimer); automationRedirectTimer = null; clearAutomationStorage(); hideAutomationProgress(); showPanelAfterAutomation(); if (message) { showToast(message); } } function beginAutomationSession() { hidePanelForAutomation(); showAutomationProgress(); } function stopAutomation() { endAutomationSession('已停止自动化提交'); } function isAutomationActive() { return Number(localStorage.getItem(REMAINING_COUNT_KEY) || 0) > 0; } function ensureTotalCountForAutomation() { const remaining = Number(localStorage.getItem(REMAINING_COUNT_KEY) || 0); const total = Number(localStorage.getItem(TOTAL_COUNT_KEY) || 0); if (remaining > 0 && total <= 0) { localStorage.setItem(TOTAL_COUNT_KEY, String(remaining)); } } function getAutomationProgress() { const remaining = Number(localStorage.getItem(REMAINING_COUNT_KEY) || 0); let total = Number(localStorage.getItem(TOTAL_COUNT_KEY) || 0); if (total <= 0) { total = remaining; } if (remaining <= 0 || total <= 0) { return null; } const completed = Math.max(0, total - remaining); const current = Math.min(total, completed + 1); const percent = Math.min(100, Math.round((completed / total) * 100)); return { current, total, percent }; } function ensureProgressModal() { injectProgressStyles(); let modal = document.querySelector('.wjx-progress-modal'); if (!modal) { modal = document.createElement('div'); modal.className = 'wjx-progress-modal'; modal.innerHTML = `

自动化提交中

第1份/共1份

进度 0%

`; document.body.appendChild(modal); modal.querySelector('#wjx-progress-stop').addEventListener('click', (event) => { event.stopPropagation(); stopAutomation(); }); } return modal; } function updateAutomationProgressUI(progress) { const countEl = document.getElementById('wjx-progress-count'); const percentEl = document.getElementById('wjx-progress-percent'); const barEl = document.getElementById('wjx-progress-bar'); if (countEl) { countEl.textContent = `第${progress.current}份/共${progress.total}份`; } if (percentEl) { percentEl.textContent = `进度 ${progress.percent}%`; } if (barEl) { barEl.style.width = `${progress.percent}%`; } } function showAutomationProgress() { const progress = getAutomationProgress(); if (!progress) { return; } const modal = ensureProgressModal(); updateAutomationProgressUI(progress); modal.classList.add('is-visible'); } function updateAutomationProgress() { const progress = getAutomationProgress(); if (!progress) { return; } const modal = document.querySelector('.wjx-progress-modal') || ensureProgressModal(); updateAutomationProgressUI(progress); modal.classList.add('is-visible'); } function hideAutomationProgress() { const modal = document.querySelector('.wjx-progress-modal'); if (modal) { modal.classList.remove('is-visible'); } } 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('脚本已生效!如果觉得好用,可以点击这里在 scriptcat 点赞一下支持作者吗?', { 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; } if (!validateCountModeBeforeStart()) { return; } localStorage.setItem(REMAINING_COUNT_KEY, String(count)); localStorage.setItem(TOTAL_COUNT_KEY, String(count)); localStorage.setItem(SURVEY_URL_KEY, location.href); if (isCountMode()) { initQuotaState(); } else { clearQuotaState(); } beginAutomationSession(); executeAutoFillAndSubmit(); } async function executeAutoFillAndSubmit() { try { if (!isAutomationActive()) { return; } showAutomationProgress(); const started = await ensureSurveyStarted(); if (!isAutomationActive()) { return; } if (!started) { throw new Error('未识别到题目页或封面开始按钮'); } const result = await fillAllQuestions(); if (!isAutomationActive()) { return; } if (result && result.submitted) { return; } // Wait a bit to look more natural await new Promise(resolve => setTimeout(resolve, 1500)); if (!isAutomationActive()) { return; } const submitted = await submitSurvey(); if (!isAutomationActive()) { return; } if (!submitted) { throw new Error('未找到提交按钮'); } } catch (error) { console.error('Automation error:', error); endAutomationSession(`提交出错: ${error.message || '未知错误'}`); } } async function fillAllQuestions() { const filledQuestionIds = new Set(); for (let pageIndex = 0; pageIndex < 12; pageIndex++) { updateAllRelationVisibility(); const visibleNodes = getVisibleQuestionNodes(); if (!visibleNodes.length) { throw new Error('当前页面未识别到可填写题目'); } const pageSignature = visibleNodes.map((node) => extractQuestionId(node, 0)).join('|'); let filledOnPage = false; for (let pass = 0; pass < 8; pass++) { updateAllRelationVisibility(); let filledInPass = false; for (const node of getVisibleQuestionNodes()) { const questionId = extractQuestionId(node, 0); if (filledQuestionIds.has(questionId)) { continue; } const relations = parseRelations(node); if (relations.length && !isRelationSatisfied(relations)) { continue; } const question = State.config.questions.find((item) => String(item.id) === String(questionId)); if (!question) { continue; } await fillQuestion(question, node); filledQuestionIds.add(questionId); updateAllRelationVisibility(); await sleep(200 + Math.random() * 300); filledInPass = true; filledOnPage = true; } if (!filledInPass) { break; } } if (!filledOnPage) { const pendingVisible = getVisibleQuestionNodes().filter((node) => { const questionId = extractQuestionId(node, 0); if (filledQuestionIds.has(questionId)) { return false; } const relations = parseRelations(node); return !relations.length || isRelationSatisfied(relations); }); if (pendingVisible.length) { throw new Error('存在未填写的关联题目,请检查父题选项是否已选中'); } } const action = await advanceSurvey(pageSignature); if (action === 'next') { continue; } if (action === 'submit') { await sleep(800 + Math.random() * 700); const submitted = await submitSurvey(); if (!submitted) { throw new Error('进入提交分支后仍未找到提交按钮'); } return { submitted: true }; } if (action === 'stalled') { throw new Error('检测到下一页按钮,但页面未成功翻页'); } return { submitted: false }; } return { submitted: false }; } async function fillQuestion(question, node) { switch (question.type) { case 'radio': case 'dropdown': case 'rating': const index = pickOptionIndexForQuestion(question); clickOption(node, index, question.type); if (isCountMode()) { consumeFlatQuota(question.id, index); } updateAllRelationVisibility(); break; case 'checkbox': const indices = pickCheckboxIndicesForQuestion(question); indices.forEach((idx) => clickOption(node, idx, question.type)); if (isCountMode()) { indices.forEach((idx) => consumeFlatQuota(question.id, idx)); } updateAllRelationVisibility(); break; case 'text': const text = pickRandomText(question.content); fillText(node, text); break; case 'slide': fillSlide(node, question.minScore, question.maxScore); break; case 'matrix': fillMatrix(node, question, question.weights); break; case 'location': await fillLocation(node); break; case 'sorting': await fillSorting(node); break; } } async function fillLocation(node) { const trigger = node.querySelector('.city-container, .divProvince, .ui-select, textarea, input[type="text"]'); 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('.reorder-list > li, .ui-sortable > li, .ui-sortable-handle, .reorder-item, ul > li')) .filter((item) => cleanText(item.textContent)); 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 isNodeVisible(node) { if (!(node instanceof HTMLElement)) { return false; } const style = window.getComputedStyle(node); return style.display !== 'none' && style.visibility !== 'hidden' && node.getClientRects().length > 0; } function getVisibleQuestionNodes() { return findQuestionNodes().filter(isNodeVisible); } function getButtonText(button) { if (!(button instanceof HTMLElement)) { return ''; } return cleanText(button.textContent || button.value || button.getAttribute('aria-label') || ''); } function getVisibleActionButtons() { return Array.from(document.querySelectorAll( '#divNext, #ctlNext, #divStart, #submit_button, .submitbutton, .btn-submit, a.button, button, .button.mainBgColor, input[type="button"], input[type="submit"]' )).filter(isNodeVisible); } function getCoverStartButton() { return getVisibleActionButtons().find((button) => { const text = getButtonText(button); if (button.matches('#divStart')) { return true; } if (button.matches('#ctlNext, #divNext') && /开始|进入|继续|下一页|下一步/.test(text)) { return true; } return /开始答题|开始填写|进入问卷|继续答题|继续填写|参与答题|立即开始|马上开始|点击开始/.test(text); }) || null; } function getNextPageButton() { return getVisibleActionButtons().find((button) => { if (button.matches('#ctlNext')) { return false; } const text = getButtonText(button); return button.id === 'divNext' || /下一页|下一步/.test(text); }) || null; } function getSubmitButton() { return getVisibleActionButtons().find((button) => { const text = getButtonText(button); if (button.matches('#submit_button, .submitbutton, .btn-submit')) { return true; } if (button.matches('#ctlNext')) { return !/下一页|下一步|开始答题|开始填写|进入问卷|继续答题|继续填写|立即开始|马上开始/.test(text); } return /提交|交卷|完成|发送|确认提交|确认/.test(text); }) || null; } async function waitForNextPage(previousSignature) { const start = Date.now(); while (Date.now() - start < 5000) { await sleep(250); const currentNodes = getVisibleQuestionNodes(); const currentSignature = currentNodes.map((node) => extractQuestionId(node, 0)).join('|'); if (currentSignature && currentSignature !== previousSignature) { return true; } } return false; } async function waitForQuestionPage() { const start = Date.now(); while (Date.now() - start < 8000) { await sleep(250); if (getVisibleQuestionNodes().length > 0) { return true; } } return false; } async function ensureSurveyStarted() { if (getVisibleQuestionNodes().length > 0) { return true; } const startButton = getCoverStartButton(); if (!startButton) { return false; } startButton.click(); return waitForQuestionPage(); } async function advanceSurvey(previousSignature) { const nextPageButton = getNextPageButton(); if (nextPageButton) { nextPageButton.click(); const pageChanged = await waitForNextPage(previousSignature); return pageChanged ? 'next' : 'stalled'; } return getSubmitButton() ? 'submit' : null; } function sleep(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } 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, type) { 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, type)) { break; } } const otherInput = option.querySelector('input.OtherText, input.OtherRadioText, .OtherText, .OtherRadioText, .ui-text input[type="text"]'); 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, type); 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 inputs = Array.from(node.querySelectorAll('textarea, input[type="text"], input[type="tel"], input[type="email"], input[type="number"]')) .filter((input) => input.offsetParent !== null && !input.classList.contains('OtherText') && !input.classList.contains('OtherRadioText')); inputs.forEach((input) => { input.value = text; input.dispatchEvent(new Event('input', { bubbles: true })); input.dispatchEvent(new Event('change', { bubbles: true })); }); } function fillSlide(node, minScore, maxScore) { const lower = clampScore(minScore, 1); const upper = Math.max(lower, clampScore(maxScore, 100)); const value = randomInt(lower, upper); const input = node.querySelector('input[type="range"], input[type="hidden"], input[type="number"], input[type="text"][id^="q"], input[name^="q"]'); if (!input) { return; } input.value = String(value); input.dispatchEvent(new Event('input', { bubbles: true })); input.dispatchEvent(new Event('change', { bubbles: true })); const visibleDisplay = node.querySelector('.slider-value, .scale-value, .slider_num, .slider-value-text'); if (visibleDisplay) { visibleDisplay.textContent = String(value); } } function getMatrixRowOptions(row) { const directControls = Array.from(row.querySelectorAll('.ui-radio, .ui-checkbox, .jqRadio, .jqCheckbox')); if (directControls.length) { return directControls; } const rateAnchors = Array.from(row.querySelectorAll('a[dval], .rate-off, .rate-on, .rate-offlarge, .rate-onlarge')); if (rateAnchors.length) { return rateAnchors; } const cells = Array.from(row.querySelectorAll('td, th')).slice(1); return cells.filter((cell) => cell.querySelector('input[type="radio"], input[type="checkbox"], .ui-radio, .ui-checkbox, a[dval], .rate-off, .rate-on') || cleanText(cell.textContent) ); } function fillMatrix(node, question, weights) { const rows = node.querySelectorAll('tr[rowindex], tbody tr'); let rowIndex = 0; rows.forEach((row) => { if (!cleanText(row.textContent)) { return; } const index = pickMatrixRowIndex(question, rowIndex); const options = getMatrixRowOptions(row); if (options[index]) { const option = options[index]; if (option.matches('a[dval], .rate-off, .rate-on, .rate-offlarge, .rate-onlarge')) { option.click(); } else if (option.matches('td, th')) { const clickable = option.querySelector('a[dval], .rate-off, .rate-on, .rate-offlarge, .rate-onlarge, .ui-radio, .ui-checkbox, input[type="radio"], input[type="checkbox"]'); if (clickable) { clickable.click(); } else { option.click(); } } else { clickChoiceElement(option, option.querySelector('input[type="checkbox"]') ? 'checkbox' : 'radio'); } if (isCountMode()) { consumeMatrixQuota(question.id, rowIndex, index); } } rowIndex += 1; }); } function isElementVisible(element) { if (!(element instanceof HTMLElement)) { return false; } const style = window.getComputedStyle(element); if (style.display === 'none' || style.visibility === 'hidden' || Number(style.opacity) === 0) { return false; } return element.getClientRects().length > 0; } async function waitForCondition(checker, timeoutMs, intervalMs) { const start = Date.now(); while (Date.now() - start < timeoutMs) { if (checker()) { return true; } await sleep(intervalMs); } return false; } const SECURITY_DIALOG_POLL_MS = 40; const SECURITY_DIALOG_CONFIRM_DELAY_MIN_MS = 800; const SECURITY_DIALOG_CONFIRM_DELAY_MAX_MS = 2000; const SECURITY_DIALOG_POST_CLICK_MS = 350; function getSecurityCheckDialog() { const layers = document.querySelectorAll('.layui-layer-dialog'); for (let i = 0; i < layers.length; i++) { const layer = layers[i]; if (!isElementVisible(layer)) { continue; } const content = layer.querySelector('.layui-layer-content'); if (content && /需要安全校验/.test(cleanText(content.textContent))) { return layer; } } return null; } function getSecurityCheckConfirmButton(layer) { const root = layer || getSecurityCheckDialog(); if (!root) { return null; } const directBtn = root.querySelector('.layui-layer-btn0'); if (directBtn && isElementVisible(directBtn)) { return directBtn; } return Array.from(root.querySelectorAll('.layui-layer-btn a, .layui-layer-btn button')).find((btn) => { return isElementVisible(btn) && /确认|确定|知道了/.test(getButtonText(btn)); }) || null; } function clickConfirmFast(element) { if (!(element instanceof HTMLElement)) { return false; } const rect = element.getBoundingClientRect(); const clientX = rect.left + rect.width / 2; const clientY = rect.top + rect.height / 2; dispatchMouseLikeEvent(element, 'pointerdown', clientX, clientY, { buttons: 1, pressure: 0.5 }); dispatchMouseLikeEvent(element, 'mousedown', clientX, clientY, { buttons: 1 }); dispatchMouseLikeEvent(element, 'pointerup', clientX, clientY, { buttons: 0, pressure: 0 }); dispatchMouseLikeEvent(element, 'mouseup', clientX, clientY, { buttons: 0 }); dispatchMouseLikeEvent(element, 'click', clientX, clientY, { buttons: 0 }); if (typeof element.click === 'function') { element.click(); } return true; } function getAliyunCaptchaPopup() { const popup = document.querySelector('#aliyunCaptcha-window-popup'); return popup && isElementVisible(popup) ? popup : null; } function getAliyunCaptchaCheckboxIcon() { const icon = document.querySelector('#aliyunCaptcha-checkbox-icon.aliyunCaptcha-checkbox-icon') || document.getElementById('aliyunCaptcha-checkbox-icon'); return icon && isElementVisible(icon) ? icon : null; } function randomFloat(min, max) { return min + Math.random() * (max - min); } function getRandomPointInElement(element, paddingRatio) { const ratio = Number.isFinite(paddingRatio) ? paddingRatio : 0.22; const rect = element.getBoundingClientRect(); if (rect.width <= 0 || rect.height <= 0) { return { clientX: rect.left, clientY: rect.top }; } const padX = Math.min(rect.width * ratio, rect.width * 0.42); const padY = Math.min(rect.height * ratio, rect.height * 0.42); const minX = rect.left + padX; const maxX = Math.max(minX, rect.right - padX); const minY = rect.top + padY; const maxY = Math.max(minY, rect.bottom - padY); return { clientX: randomFloat(minX, maxX), clientY: randomFloat(minY, maxY) }; } function buildPointerInit(clientX, clientY, extra) { return Object.assign({ bubbles: true, cancelable: true, view: window, clientX, clientY, screenX: window.screenX + clientX, screenY: window.screenY + clientY, button: 0, buttons: 0, detail: 1, pointerId: 1, pointerType: 'mouse', isPrimary: true, width: 1, height: 1, pressure: 0.5 }, extra || {}); } function dispatchMouseLikeEvent(target, type, clientX, clientY, extra) { if (!(target instanceof HTMLElement)) { return; } const mouseInit = buildPointerInit(clientX, clientY, extra); if (type.startsWith('pointer') && typeof PointerEvent !== 'undefined') { const pointerInit = Object.assign({}, mouseInit, { pressure: type === 'pointerdown' ? 0.5 : 0 }); target.dispatchEvent(new PointerEvent(type, pointerInit)); } if (/^mouse|click|dblclick$/.test(type)) { target.dispatchEvent(new MouseEvent(type, mouseInit)); } } function getEventTargetAt(clientX, clientY, fallback) { const hit = document.elementFromPoint(clientX, clientY); return hit instanceof HTMLElement ? hit : fallback; } function collectEventTargets(hitTarget, rootElement) { const chain = []; let node = hitTarget; while (node instanceof HTMLElement) { chain.push(node); if (node === rootElement) { break; } node = node.parentElement; } if (rootElement instanceof HTMLElement && !chain.includes(rootElement)) { chain.push(rootElement); } return chain; } async function simulateMouseMovePath(endX, endY, startX, startY) { const steps = randomInt(10, 22); for (let i = 1; i <= steps; i++) { const progress = i / steps; const eased = progress * progress * (3 - 2 * progress); const clientX = startX + (endX - startX) * eased + randomFloat(-1.8, 1.8); const clientY = startY + (endY - startY) * eased + randomFloat(-1.8, 1.8); const target = getEventTargetAt(clientX, clientY, document.body); dispatchMouseLikeEvent(target, 'mousemove', clientX, clientY, { buttons: 0 }); if (typeof PointerEvent !== 'undefined') { dispatchMouseLikeEvent(target, 'pointermove', clientX, clientY, { buttons: 0 }); } await sleep(randomInt(14, 42)); } } async function simulateHumanClick(element, options) { if (!(element instanceof HTMLElement)) { return false; } const settings = Object.assign({ paddingRatio: 0.22, preDelay: [120, 380], hoverDelay: [60, 180], pressDelay: [55, 140], postDelay: [80, 200], approachDistance: [45, 110] }, options || {}); await sleep(randomInt(settings.preDelay[0], settings.preDelay[1])); const point = getRandomPointInElement(element, settings.paddingRatio); const clientX = point.clientX; const clientY = point.clientY; const approach = settings.approachDistance; const startX = clientX + randomFloat(-approach[1], approach[1]); const startY = clientY + randomFloat(-approach[0], approach[0]); await simulateMouseMovePath(clientX, clientY, startX, startY); await sleep(randomInt(settings.hoverDelay[0], settings.hoverDelay[1])); const hitTarget = getEventTargetAt(clientX, clientY, element); const eventTargets = collectEventTargets(hitTarget, element); eventTargets.forEach((target) => { dispatchMouseLikeEvent(target, 'mouseover', clientX, clientY, { buttons: 0 }); dispatchMouseLikeEvent(target, 'mouseenter', clientX, clientY, { buttons: 0 }); if (typeof PointerEvent !== 'undefined') { dispatchMouseLikeEvent(target, 'pointerover', clientX, clientY, { buttons: 0 }); dispatchMouseLikeEvent(target, 'pointerenter', clientX, clientY, { buttons: 0 }); } }); await sleep(randomInt(settings.pressDelay[0], settings.pressDelay[1])); const clickTarget = hitTarget.closest('#aliyunCaptcha-checkbox-icon, #aliyunCaptcha-checkbox-body, #aliyunCaptcha-checkbox-left, #aliyunCaptcha-checkbox-wrapper') || element; dispatchMouseLikeEvent(clickTarget, 'pointerdown', clientX, clientY, { buttons: 1, pressure: 0.5 }); dispatchMouseLikeEvent(clickTarget, 'mousedown', clientX, clientY, { buttons: 1 }); await sleep(randomInt(45, 125)); dispatchMouseLikeEvent(clickTarget, 'pointerup', clientX, clientY, { buttons: 0, pressure: 0 }); dispatchMouseLikeEvent(clickTarget, 'mouseup', clientX, clientY, { buttons: 0 }); dispatchMouseLikeEvent(clickTarget, 'click', clientX, clientY, { buttons: 0 }); await sleep(randomInt(settings.postDelay[0], settings.postDelay[1])); return true; } async function handleAliyunCaptchaCheckbox(timeoutMs) { const appeared = await waitForCondition(() => getAliyunCaptchaPopup(), timeoutMs, SECURITY_DIALOG_POLL_MS); if (!appeared) { return false; } return performAliyunCaptchaClick(timeoutMs); } async function performAliyunCaptchaClick(timeoutMs) { const icon = getAliyunCaptchaCheckboxIcon() || document.querySelector('#aliyunCaptcha-checkbox-body') || document.querySelector('#aliyunCaptcha-checkbox-left'); if (!icon) { return false; } await sleep(randomInt(450, 1100)); await simulateHumanClick(icon, { paddingRatio: 0.18, preDelay: [180, 520], hoverDelay: [90, 240], pressDelay: [70, 160], postDelay: [120, 280], approachDistance: [55, 130] }); await sleep(randomInt(600, 1200)); const loading = document.querySelector('#aliyunCaptcha-loading'); if (loading && isElementVisible(loading)) { await waitForCondition(() => { return !isElementVisible(loading) || !!document.querySelector('.aliyunCaptcha-checkbox-icon-checked'); }, Math.min(timeoutMs, 12000), 400); } await waitForCondition(() => !getAliyunCaptchaPopup(), Math.min(timeoutMs, 15000), 400); return true; } async function handleVerification(timeoutMs) { const deadline = Date.now() + (timeoutMs || 12000); let handled = false; let securityConfirmed = false; let aliyunClicked = false; let sliderHandled = false; let idleSince = Date.now(); while (Date.now() < deadline) { let activeThisTick = false; if (!securityConfirmed) { const confirmBtn = getSecurityCheckConfirmButton(getSecurityCheckDialog()); if (confirmBtn) { await sleep(randomInt(SECURITY_DIALOG_CONFIRM_DELAY_MIN_MS, SECURITY_DIALOG_CONFIRM_DELAY_MAX_MS)); clickConfirmFast(confirmBtn); await sleep(SECURITY_DIALOG_POST_CLICK_MS); securityConfirmed = true; handled = true; activeThisTick = true; idleSince = Date.now(); } } if (!aliyunClicked && getAliyunCaptchaPopup()) { if (await performAliyunCaptchaClick(deadline - Date.now())) { aliyunClicked = true; handled = true; activeThisTick = true; idleSince = Date.now(); } } if (!sliderHandled) { const rectMask = document.querySelector('#rectMask'); if (rectMask && isElementVisible(rectMask)) { rectMask.click(); await sleep(2000); await simulateSlider(); sliderHandled = true; handled = true; activeThisTick = true; idleSince = Date.now(); } } if (activeThisTick) { await sleep(SECURITY_DIALOG_POLL_MS); continue; } if (Date.now() - idleSince >= 1200) { break; } await sleep(SECURITY_DIALOG_POLL_MS); } return handled; } async function submitSurvey() { const submitBtn = getSubmitButton(); if (!submitBtn) { return false; } submitBtn.click(); const verificationHandled = await handleVerification(); if (verificationHandled) { await sleep(600); const retrySubmit = getSubmitButton(); if (retrySubmit) { retrySubmit.click(); await handleVerification(); } } else { await sleep(800); } return true; } 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(); })();