Pinnacle Optimize

添加作者

  1. // ==UserScript==
  2. // @name Pinnacle Optimize
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.5
  5. // @description 添加作者
  6. // @author Lycoiref & xgay231
  7. // @match *://app.pinnacle.run/*
  8. // @icon https://www.google.com/s2/favicons?sz=64&domain=mjclouds.com
  9. // @license MIT
  10. // @grant none
  11. // ==/UserScript==
  12.  
  13. (function () {
  14. 'use strict';
  15.  
  16. let optionsRows = [];
  17. let btns = [];
  18. let currentQuestionTextElement = null; // 存储当前问题文本元素
  19. let transformingContainer = null;
  20. let mainContentArea = null;
  21.  
  22. // 封装更健壮的点击事件模拟函数,模拟完整的鼠标点击过程
  23. function simulateFullClick(element) {
  24. if (!element) {
  25. console.error('simulateFullClick called with null or undefined element.');
  26. return;
  27. }
  28. try {
  29. // 获取元素的位置和大小,计算中心点作为点击坐标
  30. const rect = element.getBoundingClientRect();
  31. const clientX = rect.left + rect.width / 2;
  32. const clientY = rect.top + rect.height / 2;
  33.  
  34. const commonEventProps = {
  35. bubbles: true,
  36. cancelable: true,
  37. view: window,
  38. clientX: clientX,
  39. clientY: clientY
  40. };
  41.  
  42. // 1. 模拟 mouseover 事件 (可选,但有时有用)
  43. element.dispatchEvent(new MouseEvent('mouseover', commonEventProps));
  44.  
  45. // 2. 模拟 mousedown 事件
  46. element.dispatchEvent(new MouseEvent('mousedown', commonEventProps));
  47. console.log('Dispatched mousedown to:', element);
  48.  
  49. // 3. 模拟 mouseup 事件
  50. element.dispatchEvent(new MouseEvent('mouseup', commonEventProps));
  51. console.log('Dispatched mouseup to:', element);
  52.  
  53. // 4. 模拟 click 事件 (这是最终触发交互的事件)
  54. element.dispatchEvent(new MouseEvent('click', commonEventProps));
  55. console.log('Dispatched click to:', element);
  56.  
  57. // 模拟 mouseout 事件 (可选,但有时有用)
  58. element.dispatchEvent(new MouseEvent('mouseout', commonEventProps));
  59.  
  60. } catch (e) {
  61. console.error('Error dispatching full click event:', e);
  62. }
  63. }
  64.  
  65. // 封装更新DOM元素引用的函数
  66. function updateElements() {
  67. // 尝试找到当前活跃的题目包装器(通过transform: none 或 translateX(0px)判断)
  68. const activeWrapper = document.querySelector(
  69. '.h-full.bg-default-555[style*="transform: none"], ' +
  70. '.h-full.bg-default-555[style*="transform: translateX(0px)"], ' +
  71. '.h-full.bg-default-555[style*="transform: translate3d(0px, 0px, 0px)"], ' +
  72. // 兜底方案:如果上述选择器都找不到,就找第一个可见的 .question-card 的父级
  73. '.h-full.bg-default-555:not([style*="opacity: 0"])'
  74. );
  75.  
  76. let questionElement = null;
  77. let optionsList = [];
  78.  
  79. if (activeWrapper) {
  80. questionElement = activeWrapper.querySelector('.whitespace-pre-line');
  81. optionsList = activeWrapper.querySelectorAll('.select-row');
  82. } else {
  83. // 如果没有找到活跃的包装器,直接查找页面上的第一个
  84. console.warn('Active question wrapper not found. Falling back to general queries.');
  85. questionElement = document.querySelector('.whitespace-pre-line');
  86. optionsList = document.querySelectorAll('.select-row');
  87. }
  88.  
  89. currentQuestionTextElement = questionElement; // 更新全局问题元素引用
  90. optionsRows = optionsList; // 更新全局选项行引用
  91. btns = document.querySelectorAll('button'); // 总是获取所有按钮
  92.  
  93. console.log('Elements updated:', {
  94. optionsRows,
  95. btns,
  96. question: currentQuestionTextElement ? currentQuestionTextElement.innerText : 'N/A',
  97. activeWrapper: activeWrapper // 调试用
  98. });
  99. }
  100.  
  101. window.onload = async () => {
  102. await new Promise((resolve) => setTimeout(resolve, 100));
  103.  
  104. // 持续等待关键DOM元素出现
  105. while (!document.querySelector('.select-row') || !document.querySelector('button') || !document.querySelector('.whitespace-pre-line')) {
  106. console.log('Waiting for essential elements...');
  107. await new Promise((resolve) => setTimeout(resolve, 100));
  108. }
  109.  
  110. // 首次加载时更新所有元素引用
  111. updateElements();
  112. console.log('Initial elements fetched.');
  113.  
  114. // 页面刷新机制(防卡死)
  115. setInterval(() => {
  116. let cards = document.querySelectorAll('p');
  117. cards.forEach((card) => {
  118. if (card.innerText.includes('题库市场')) {
  119. console.log('Detected "题库市场" card, reloading page to prevent being stuck.');
  120. window.location.reload();
  121. }
  122. });
  123. }, 500);
  124.  
  125. window.addEventListener('keypress', (e) => {
  126. const keyLower = e.key.toLowerCase();
  127. const targetIndex = ['q', 'w', 'e', 'r'].indexOf(keyLower);
  128.  
  129. if (targetIndex !== -1) {
  130. // 检查整个选项组是否被禁用
  131. const optionsGroupParent = optionsRows[targetIndex]?.closest('[data-sentry-component="OptionsGroup"]');
  132. if (optionsGroupParent && optionsGroupParent.classList.contains('disabled')) {
  133. console.log('Options group is disabled. Cannot select a new option.');
  134. return;
  135. }
  136.  
  137. if (optionsRows[targetIndex]) {
  138. const clickableTarget = optionsRows[targetIndex].querySelector('section.content') || optionsRows[targetIndex];
  139. if (clickableTarget) {
  140. console.log(`Attempting to click option ${targetIndex + 1} with key '${keyLower}':`, clickableTarget);
  141. simulateFullClick(clickableTarget); // 使用 simulateFullClick
  142. } else {
  143. console.warn(`Clickable target (section.content) not found within option row ${targetIndex + 1}.`);
  144. }
  145. } else {
  146. console.warn(`Option row for key '${keyLower}' (index ${targetIndex}) not found. There might be fewer options.`);
  147. }
  148. }
  149.  
  150. if (e.key === ' ') {
  151. e.preventDefault();
  152. console.log('Spacebar pressed. Current buttons:', btns);
  153.  
  154. let nextButton = null;
  155. let submitButton = null;
  156.  
  157. for (let i = 0; i < btns.length; i++) {
  158. const btnText = btns[i].innerText;
  159. if (btnText.includes('下一题')) {
  160. nextButton = btns[i];
  161. }
  162. if (btnText.includes('提交')) {
  163. submitButton = btns[i];
  164. }
  165. }
  166.  
  167. if (nextButton && !nextButton.disabled) {
  168. simulateFullClick(nextButton); // 使用 simulateFullClick
  169. console.log('Clicked "下一题" button.');
  170. } else if (submitButton && !submitButton.disabled) {
  171. simulateFullClick(submitButton); // 使用 simulateFullClick
  172. console.log('Clicked "提交" button.');
  173. } else {
  174. console.log('No clickable "下一题" or "提交" button found, or they are disabled.');
  175. }
  176. document.activeElement.blur();
  177. }
  178. });
  179.  
  180. // MutationObserver 的目标是整体内容区域或包含动画的容器
  181. transformingContainer = document.querySelector('.h-full.bg-default-555[draggable="false"]');
  182. mainContentArea = document.querySelector('.flex-grow.flex.flex-col.mx-auto.w-full.max-w-lg');
  183. const observerTarget = mainContentArea; // 观察最外层稳定区域
  184.  
  185. if (observerTarget) {
  186. let observer = new MutationObserver((mutations) => {
  187. let shouldUpdate = false;
  188. for (const mutation of mutations) {
  189. // 检查是否有子节点增删,或者文本内容变化 (在题目卡片内部)
  190. if (mutation.type === 'childList' || mutation.type === 'characterData') {
  191. // 检查变化是否发生在题目相关的元素内部
  192. const targetElement = mutation.target;
  193. if (targetElement.closest('.question-card') || targetElement.matches('.question-card') ||
  194. targetElement.closest('.select-row') || targetElement.matches('.select-row') ||
  195. (currentQuestionTextElement && (targetElement === currentQuestionTextElement || currentQuestionTextElement.contains(targetElement)))) {
  196. shouldUpdate = true;
  197. break;
  198. }
  199. }
  200. // 检查 transform 样式变化,因为这是题目切换的关键信号
  201. if (mutation.type === 'attributes' && mutation.attributeName === 'style' && transformingContainer && mutation.target === transformingContainer) {
  202. const newTransform = mutation.target.style.transform;
  203. if (newTransform && newTransform !== mutation.oldValue) {
  204. console.log('Transforming container style changed. Potential question switch.');
  205. shouldUpdate = true;
  206. break;
  207. }
  208. }
  209. }
  210.  
  211. if (shouldUpdate) {
  212. // 添加一个短延迟,确保动画完成和DOM稳定
  213. clearTimeout(window.pinnacleUpdateTimer);
  214. window.pinnacleUpdateTimer = setTimeout(() => {
  215. console.log('Delayed updateElements triggered.');
  216. updateElements();
  217. }, 200); // 200毫秒延迟,可根据动画时间调整
  218. }
  219. });
  220.  
  221. observer.observe(observerTarget, {
  222. childList: true,
  223. subtree: true,
  224. attributes: true,
  225. attributeFilter: ['style', 'class'], // 监听 style 和 class 属性
  226. characterData: true
  227. });
  228. console.log('MutationObserver started on:', observerTarget);
  229. } else {
  230. console.warn('Neither transforming container nor main content area found for MutationObserver.');
  231. }
  232. };
  233. })();