utilsmk

岐黄天使刷课助手的工具函数模块,提供通用的辅助功能,如模拟点击、延迟等。

此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.gf.qytechs.cn/scripts/537081/1594864/utilsmk.js

  1. // ==UserScript==
  2. // @name 岐黄天使刷课助手 - 工具函数模块
  3. // @namespace http://tampermonkey.net/qhtx-modules
  4. // @version 1.3.0
  5. // @description 岐黄天使刷课助手的工具函数模块,提供通用的辅助功能,如模拟点击、延迟等。
  6. // @author AI助手
  7. // ==/UserScript==
  8.  
  9. // 工具函数模块
  10. (function() {
  11. 'use strict';
  12.  
  13. // 记录最近点击的元素,防止重复点击
  14. let lastClickedElement = null;
  15. let lastClickTime = 0;
  16. const MIN_CLICK_INTERVAL = 500; // 最小点击间隔(毫秒)
  17.  
  18. // 模拟点击元素
  19. // 新增模块加载日志功能
  20. function logModuleLoad(moduleName) {
  21. const timestamp = new Date().toISOString().slice(11, 23);
  22. console.log(`[${timestamp}] 模块加载追踪: ${moduleName} 已初始化完成`);
  23. GM_setValue('last_module_load', {
  24. module: moduleName,
  25. time: Date.now()
  26. });
  27. }
  28.  
  29. // 增强模拟点击函数日志
  30. function simulateClick(element, question) {
  31. // 安全地访问moduleStatus
  32. if (window.moduleStatus && window.moduleStatus.loaded) {
  33. console.log('[模块状态] 当前已加载模块:', Array.from(window.moduleStatus.loaded));
  34. } else {
  35. console.log('[模块状态] moduleStatus未定义或未初始化');
  36. }
  37. try {
  38. // 防止重复点击同一元素
  39. const now = Date.now();
  40. if (element === lastClickedElement && now - lastClickTime < MIN_CLICK_INTERVAL) {
  41. console.log('防止重复点击:', element.textContent || element.id || element.className);
  42. return;
  43. }
  44.  
  45. // 更新最近点击记录
  46. lastClickedElement = element;
  47. lastClickTime = now;
  48.  
  49. // 检查元素是否在iframe中
  50. const isInIframe = question && question.isInIframe;
  51. const iframe = question && question.iframe;
  52. const targetWindow = isInIframe ? iframe.contentWindow : window;
  53.  
  54. // 模拟鼠标移动到元素上
  55. const mouseoverEvent = new MouseEvent('mouseover', {
  56. view: targetWindow,
  57. bubbles: true,
  58. cancelable: true
  59. });
  60. element.dispatchEvent(mouseoverEvent);
  61.  
  62. // 直接模拟点击,不添加延迟
  63. // 模拟鼠标按下
  64. const mousedownEvent = new MouseEvent('mousedown', {
  65. view: targetWindow,
  66. bubbles: true,
  67. cancelable: true
  68. });
  69. element.dispatchEvent(mousedownEvent);
  70.  
  71. // 模拟鼠标释放
  72. const mouseupEvent = new MouseEvent('mouseup', {
  73. view: targetWindow,
  74. bubbles: true,
  75. cancelable: true
  76. });
  77. element.dispatchEvent(mouseupEvent);
  78.  
  79. // 模拟点击
  80. const clickEvent = new MouseEvent('click', {
  81. view: targetWindow,
  82. bubbles: true,
  83. cancelable: true
  84. });
  85. element.dispatchEvent(clickEvent);
  86.  
  87. // 如果元素有onclick属性,直接调用
  88. if (element.onclick) {
  89. element.onclick();
  90. }
  91.  
  92. // 如果元素有choose函数,尝试调用
  93. if (isInIframe) {
  94. // 在iframe中尝试调用choose函数
  95. if (typeof iframe.contentWindow.choose === 'function' && element.classList.contains('option')) {
  96. iframe.contentWindow.choose(element);
  97. }
  98. } else {
  99. // 在主窗口中尝试调用choose函数
  100. if (typeof choose === 'function' && element.classList.contains('option')) {
  101. choose(element);
  102. }
  103. }
  104.  
  105. // 如果元素有setti函数,尝试调用(用于题目导航)
  106. if (element.getAttribute('onclick') && element.getAttribute('onclick').includes('setti(')) {
  107. try {
  108. const onclickAttr = element.getAttribute('onclick');
  109. const match = onclickAttr.match(/setti\((\d+)/);
  110. if (match && match[1]) {
  111. const questionNumber = parseInt(match[1]);
  112. if (isInIframe) {
  113. if (typeof iframe.contentWindow.setti === 'function') {
  114. iframe.contentWindow.setti(questionNumber, element);
  115. }
  116. } else {
  117. if (typeof setti === 'function') {
  118. setti(questionNumber, element);
  119. }
  120. }
  121. }
  122. } catch (e) {
  123. console.error('调用setti函数失败:', e);
  124. }
  125. }
  126.  
  127. console.log('模拟点击元素:', element.textContent || element.id || element.className);
  128.  
  129. // 添加网络请求监控
  130. const originalFetch = window.fetch;
  131. const originalXHROpen = XMLHttpRequest.prototype.open;
  132.  
  133. // 请求跟踪器
  134. const requestTracker = {
  135. startMonitoring() {
  136. window.fetch = this.wrapFetch;
  137. XMLHttpRequest.prototype.open = this.wrapXHROpen;
  138. this.cleanupTimer = setTimeout(() => this.stopMonitoring(), 5000);
  139. },
  140. stopMonitoring() {
  141. window.fetch = originalFetch;
  142. XMLHttpRequest.prototype.open = originalXHROpen;
  143. clearTimeout(this.cleanupTimer);
  144. },
  145. wrapFetch: async function(input, init) {
  146. const startTime = Date.now();
  147. try {
  148. const response = await originalFetch(input, init);
  149. console.log('[网络请求] FETCH成功:', {
  150. url: input.url || input,
  151. status: response.status,
  152. duration: Date.now() - startTime + 'ms'
  153. });
  154. return response;
  155. } catch (error) {
  156. console.log('[网络请求] FETCH失败:', {
  157. url: input.url || input,
  158. error: error.message
  159. });
  160. throw error;
  161. }
  162. },
  163. wrapXHROpen: function(method, url) {
  164. this._requestStartTime = Date.now();
  165. this.addEventListener('loadend', () => {
  166. console.log('[网络请求] XHR完成:', {
  167. method: method,
  168. url: url,
  169. status: this.status,
  170. duration: Date.now() - this._requestStartTime + 'ms'
  171. });
  172. });
  173. return originalXHROpen.apply(this, arguments);
  174. }
  175. };
  176.  
  177. // 启动请求监控
  178. requestTracker.startMonitoring();
  179. } catch (e) {
  180. console.error('模拟点击出错:', e);
  181.  
  182. // 尝试直接点击
  183. try {
  184. if (element.click) {
  185. element.click();
  186. }
  187. } catch (e2) {
  188. console.error('直接点击也失败:', e2);
  189. }
  190. }
  191. };
  192.  
  193. // 获取随机延迟时间
  194. function getRandomDelay(range) {
  195. return Math.floor(Math.random() * (range.max - range.min + 1)) + range.min;
  196. }
  197.  
  198. // 关闭弹窗
  199. function closePopups(doc) {
  200. // 尝试关闭各种可能的弹窗
  201. const closeButtons = doc.querySelectorAll('.close-btn, .close, .popup-close, .messager-button a');
  202. closeButtons.forEach(btn => {
  203. try {
  204. btn.click();
  205. } catch (e) {
  206. console.error('关闭弹窗失败:', e);
  207. }
  208. });
  209.  
  210. // 特别处理messager-window弹窗
  211. const messagerWindows = doc.querySelectorAll('.messager-window');
  212. if (messagerWindows.length > 0) {
  213. messagerWindows.forEach(window => {
  214. try {
  215. const closeBtn = window.querySelector('.messager-button a');
  216. if (closeBtn) {
  217. closeBtn.click();
  218. }
  219. } catch (e) {
  220. console.error('关闭messager-window失败:', e);
  221. }
  222. });
  223. }
  224. };
  225.  
  226. // 检查页面类型
  227. function checkPageType() {
  228. // 确定当前页面类型
  229. let pageType = 'unknown';
  230.  
  231. if (window.location.href.includes('courseLearn.html')) {
  232. pageType = 'courseLearn';
  233. } else if (window.location.href.includes('courseSimulate.html')) {
  234. pageType = 'courseSimulate';
  235. } else if (window.location.href.includes('courseExam.html')) {
  236. pageType = 'courseExam';
  237. } else if (window.location.href.includes('courseList.html')) {
  238. pageType = 'courseList';
  239. } else if (window.location.href.includes('视频播放.html')) {
  240. pageType = 'videoPlay';
  241. }
  242.  
  243. console.log('当前页面类型:', pageType);
  244.  
  245. // 根据页面类型执行不同操作
  246. switch (pageType) {
  247. case 'courseLearn':
  248. // 在课程学习页面,检查是否应该自动开始
  249. let isRunning;
  250. if (typeof GM_getValue !== 'undefined') {
  251. isRunning = GM_getValue('qh-is-running', false);
  252. } else {
  253. isRunning = localStorage.getItem('qh-is-running') === 'true';
  254. }
  255.  
  256. if (isRunning) {
  257. console.log('自动学习已启用,尝试播放视频');
  258. // 延迟执行,确保页面加载完成
  259. setTimeout(() => {
  260. if (typeof playAllVideos === 'function') {
  261. playAllVideos();
  262. }
  263. }, 2000);
  264. }
  265. break;
  266.  
  267. case 'courseSimulate':
  268. case 'courseExam':
  269. // 在模拟练习或考试页面,添加自动答题按钮
  270. if (window.qh.autoFlowConfig.enabled && typeof startAutoAnswer === 'function') {
  271. setTimeout(() => {
  272. startAutoAnswer();
  273. }, 2000);
  274. }
  275. break;
  276.  
  277. case 'courseList':
  278. // 在课程列表页面,可以添加一些特定操作
  279. console.log('在课程列表页面,可以选择课程开始学习');
  280. break;
  281.  
  282. case 'videoPlay':
  283. // 在视频播放页面,初始化视频导航
  284. console.log('在视频播放页面,初始化视频导航');
  285. if (typeof initVideoNavigation === 'function') {
  286. setTimeout(() => {
  287. initVideoNavigation();
  288. }, 2000);
  289. }
  290. break;
  291.  
  292. default:
  293. // 即使在其他页面,也尝试检测和播放视频
  294. setTimeout(() => {
  295. const videos = document.querySelectorAll('video');
  296. if (videos.length > 0) {
  297. console.log('在其他页面检测到视频,尝试播放');
  298. if (typeof playAllVideos === 'function') {
  299. playAllVideos();
  300. }
  301. }
  302. }, 3000);
  303. break;
  304. }
  305. }
  306.  
  307. // 导出模块函数
  308. window.utils = checkPageType;
  309. window.simulateClick = simulateClick;
  310. window.getRandomDelay = getRandomDelay;
  311. window.closePopups = closePopups;
  312. window.logModuleLoad = logModuleLoad;
  313. window.checkPageType = checkPageType;
  314.  
  315. // 通知主脚本模块已加载
  316. console.log('[模块加载] utils 模块已加载');
  317. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址