Files
vm-userscripts/Submit Click (F12) + Copy CURRENT Visible Question (F10).user.js
T
2026-06-15 14:20:38 -04:00

213 lines
9.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// ==UserScript==
// @name Submit Click (F12) + Copy CURRENT Visible Question (F10)
// @namespace vm-submit-click-and-copy-visible-text
// @match https://*netacad.com/*
// @require https://cdn.jsdelivr.net/npm/@violentmonkey/shortcut@1
// @grant none
// @version 2.0
// @run-at document-end
// ==/UserScript==
(function () {
'use strict';
// ────────────────────────────────────────────────
// Submit button click (F12) unchanged
// ────────────────────────────────────────────────
function findSubmitButton() {
let btns = document.querySelectorAll('button[aria-label="submit"].submit-button');
for (const btn of btns) {
const innerDiv = btn.querySelector('div.submit');
if (innerDiv && innerDiv.textContent.trim() === 'Submit') return btn;
}
function searchShadows(root) {
const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT);
let node;
while ((node = walker.nextNode())) {
if (node.shadowRoot) {
const shadowBtns = node.shadowRoot.querySelectorAll('button[aria-label="submit"].submit-button');
for (const btn of shadowBtns) {
const innerDiv = btn.querySelector('div.submit');
if (innerDiv && innerDiv.textContent.trim() === 'Submit') return btn;
}
const found = searchShadows(node.shadowRoot);
if (found) return found;
}
}
return null;
}
return searchShadows(document.body);
}
function triggerSubmitClick() {
const btn = findSubmitButton();
if (!btn) return false;
try { btn.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true, view: window })); } catch {}
if (btn.click) try { btn.click(); } catch {}
return true;
}
// ────────────────────────────────────────────────
// Copy CURRENT visible question text (F10) improved
// ────────────────────────────────────────────────
function isVisibleAndInView(el) {
if (!el) return false;
const style = window.getComputedStyle(el);
if (style.display === 'none' || style.visibility === 'hidden' || parseFloat(style.opacity) < 0.1) return false;
let current = el;
let depth = 0;
while (current && depth < 15) {
const s = window.getComputedStyle(current);
if (s.display === 'none' || s.visibility === 'hidden' || parseFloat(s.opacity) < 0.1) return false;
current = current.parentElement;
depth++;
}
const rect = el.getBoundingClientRect();
if (rect.width < 50 || rect.height < 20) return false; // too tiny for question text
// Roughly in viewport
return rect.top > -50 && rect.bottom < (window.innerHeight + 50) &&
rect.left > -50 && rect.right < (window.innerWidth + 50);
}
function findCurrentQuestionSpan() {
console.log('[TextCopy] Searching for CURRENT visible question (strict mode)...');
const containerSelectors = [
'[class*="body-inner"]',
'.mcq__body-inner',
'.component__body-inner',
'[class*="mcq__body"]',
'[class*="component__body"]'
];
const spanSelectors = ['p span', 'p > span', 'span'];
let candidates = [];
// Light DOM
containerSelectors.forEach(sel => {
document.querySelectorAll(sel).forEach(container => {
if (!isVisibleAndInView(container)) return;
spanSelectors.forEach(sSel => {
container.querySelectorAll(sSel).forEach(span => {
const txt = span.textContent?.trim();
if (!txt || txt.length < 20) return;
if (!isVisibleAndInView(span)) return;
const rect = span.getBoundingClientRect();
const area = rect.width * rect.height;
const z = parseFloat(window.getComputedStyle(span).zIndex) || 0;
const hasQuestionMark = txt.includes('?');
candidates.push({
span,
txt,
area,
zIndex: isNaN(z) ? 0 : z,
hasQuestionMark,
depth: span.compareDocumentPosition(document.body) // rough DOM order
});
});
});
});
});
// Shadow DOM
function walkShadows(root) {
const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT);
let node;
while ((node = walker.nextNode())) {
if (node.shadowRoot) {
containerSelectors.forEach(sel => {
node.shadowRoot.querySelectorAll(sel).forEach(container => {
if (!isVisibleAndInView(container)) return;
spanSelectors.forEach(sSel => {
container.querySelectorAll(sSel).forEach(span => {
const txt = span.textContent?.trim();
if (!txt || txt.length < 20) return;
if (!isVisibleAndInView(span)) return;
const rect = span.getBoundingClientRect();
const area = rect.width * rect.height;
const z = parseFloat(window.getComputedStyle(span).zIndex) || 0;
const hasQuestionMark = txt.includes('?');
candidates.push({
span,
txt,
area,
zIndex: isNaN(z) ? 0 : z,
hasQuestionMark,
depth: span.compareDocumentPosition(document.body)
});
});
});
});
});
walkShadows(node.shadowRoot);
}
}
}
walkShadows(document.body);
if (candidates.length === 0) {
console.log('[TextCopy] No visible candidates found');
return null;
}
// Sort: prefer question mark → higher z-index → larger area → earlier in DOM
candidates.sort((a, b) => {
if (a.hasQuestionMark !== b.hasQuestionMark) return b.hasQuestionMark - a.hasQuestionMark;
if (a.zIndex !== b.zIndex) return b.zIndex - a.zIndex;
if (a.area !== b.area) return b.area - a.area;
return a.depth - b.depth;
});
const best = candidates[0];
console.log('[TextCopy] Selected:', best.txt.substring(0, 120) + (best.txt.length > 120 ? '...' : ''));
console.log('[TextCopy] Stats → area:', best.area, '| z-index:', best.zIndex, '| has ?:', best.hasQuestionMark, '| candidates total:', candidates.length);
return best.span;
}
async function copyCurrentQuestion() {
const el = findCurrentQuestionSpan();
if (!el) return;
const text = el.textContent?.trim() || '';
if (!text) return;
console.log('[TextCopy] Copying:', text.substring(0, 120) + (text.length > 120 ? '...' : ''));
try {
await navigator.clipboard.writeText(text);
console.log('[TextCopy] SUCCESS current question copied');
} catch (err) {
console.error('[TextCopy] Clipboard failed:', err);
const ta = document.createElement('textarea');
ta.value = text;
ta.style.position = 'fixed'; ta.style.opacity = '0';
document.body.appendChild(ta);
ta.select();
document.execCommand('copy');
document.body.removeChild(ta);
console.log('[TextCopy] Fallback copy done');
}
}
// ────────────────────────────────────────────────
// Hotkeys
// ────────────────────────────────────────────────
VM.shortcut.register('f12', () => {
setTimeout(() => { if (!triggerSubmitClick()) setTimeout(triggerSubmitClick, 400); }, 60);
});
VM.shortcut.register('f10', copyCurrentQuestion);
console.log('[VM-Tools] Loaded v2.0 → F12 = Submit | F10 = Copy CURRENT visible question (strict visibility)');
})();