您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
track the detailed submission status on ACS Publishing Platform.
// ==UserScript== // @name ACS Tracker // @namespace http://tampermonkey.net/ // @version 1.2 // @description track the detailed submission status on ACS Publishing Platform. // @author zhangkaihua88 // @match *://publish.acs.org/app* // @icon https://www.google.com/s2/favicons?sz=64&domain=acs.org // @grant none // @license Apache-2.0 // ==/UserScript== /** * 监听包含特定字符串的所有API请求并返回响应数据 * @param {string} targetString - 要匹配的目标字符串 * @param {number} [timeout=30000] - 超时时间(毫秒) * @param {boolean} [captureAll=false] - 是否捕获所有匹配请求(否则只捕获第一个) * @returns {Promise<Object|Object[]>} - 包含响应数据的Promise(单个或数组) */ function listenForApiResponse(targetString, timeout = 30000, captureAll = false) { return new Promise((resolve, reject) => { // 用于存储原始 fetch 和 XMLHttpRequest 方法 const originalFetch = window.fetch; const originalXhrOpen = XMLHttpRequest.prototype.open; // 存储所有匹配的响应 const matchedResponses = []; // 超时计时器 const timeoutId = setTimeout(() => { resetInterceptors(); if (matchedResponses.length > 0) { resolve(captureAll ? matchedResponses : matchedResponses[0]); } else { reject(new Error(`监听超时: ${timeout}ms 内未捕获到包含 "${targetString}" 的请求`)); } }, timeout); // 重置拦截器函数 function resetInterceptors() { window.fetch = originalFetch; XMLHttpRequest.prototype.open = originalXhrOpen; } // 检查 URL 是否包含目标字符串 function containsTargetString(url) { return url.toString().includes(targetString); } // 处理匹配的响应 function handleMatchedResponse(data) { matchedResponses.push(data); if (!captureAll) { clearTimeout(timeoutId); resetInterceptors(); resolve(data); } } // 拦截 Fetch API window.fetch = async function (input, init) { const url = typeof input === 'string' ? input : input.url; try { const response = await originalFetch.apply(this, arguments); // 如果URL包含目标字符串,获取响应数据 if (containsTargetString(url)) { const responseClone = response.clone(); const contentType = responseClone.headers.get('content-type'); let responseData; if (contentType && contentType.includes('application/json')) { responseData = await responseClone.json(); } else { responseData = await responseClone.text(); } handleMatchedResponse({ url, method: init?.method || 'GET', status: response.status, data: responseData }); } return response; } catch (error) { // 如果出错且URL包含目标字符串,记录错误 if (containsTargetString(url)) { handleMatchedResponse({ url, method: init?.method || 'GET', error: error.message }); } throw error; } }; // 拦截 XMLHttpRequest XMLHttpRequest.prototype.open = function (method, url) { const xhr = this; // 监听状态变化 xhr.addEventListener('readystatechange', function () { if (xhr.readyState === 4 && containsTargetString(url)) { let responseData; try { responseData = JSON.parse(xhr.responseText); } catch (e) { responseData = xhr.responseText; } handleMatchedResponse({ url, method, status: xhr.status, data: responseData }); } }); return originalXhrOpen.apply(this, arguments); }; }); } function convertToBeijingTime(dateString) { // 检查是否为特殊日期 if (dateString === "0001-01-01T00:00:00") { return ""; } const date = new Date(dateString); // Convert to Beijing Time (UTC +8) using toLocaleString with options return date.toLocaleString("zh-CN", { timeZone: "Asia/Shanghai", // Specify Beijing timezone hour12: false // Use 24-hour format }); } function createTable(data) { // Create the table const table = document.createElement('table'); // Apply basic styles table.style.width = '100%'; table.style.borderCollapse = 'collapse'; table.style.margin = '20px 0'; table.style.fontFamily = 'Arial, sans-serif'; table.style.boxShadow = '0 2px 12px rgba(0, 0, 0, 0.1)'; // Create table header const header = table.createTHead(); const headerRow = header.insertRow(0); const headers = ["Task ID", "Task Name", "Task Status Name", "Status", "Started", "Completed", "Due"]; // Apply header styles headers.forEach((headerText) => { const th = document.createElement('th'); th.textContent = headerText; th.style.backgroundColor = '#4CAF50'; th.style.color = 'white'; th.style.padding = '10px'; th.style.textAlign = 'left'; th.style.fontSize = '16px'; headerRow.appendChild(th); }); // Create table body const body = table.createTBody(); data.forEach(task => { const row = body.insertRow(); // Apply alternating row colors if (body.rows.length % 2 === 0) { row.style.backgroundColor = '#f9f9f9'; // even row } row.addEventListener('mouseenter', () => { row.style.backgroundColor = '#f5f5f5'; // hover effect }); row.addEventListener('mouseleave', () => { row.style.backgroundColor = body.rows.length % 2 === 0 ? '#f9f9f9' : ''; // revert back to alternate color }); // Insert the task data into the table row.insertCell(0).textContent = task.taskId; row.insertCell(1).textContent = task.taskName; row.insertCell(2).textContent = task.taskStatusName; row.insertCell(3).textContent = task.taskStatus; row.insertCell(4).textContent = convertToBeijingTime(task.datetimeStarted); row.insertCell(5).textContent = convertToBeijingTime(task.datetimeCompleted); row.insertCell(6).textContent = convertToBeijingTime(task.datetimeDue); // Apply cell styles Array.from(row.cells).forEach(cell => { cell.style.padding = '8px'; cell.style.textAlign = 'left'; cell.style.borderBottom = '1px solid #ddd'; cell.style.fontSize = '14px'; }); }); return table; } (function () { function startListening() { listenForApiResponse('integration/s1/submissions/submissionInfo?') .then(result => { console.log('匹配的API响应:', result.data.response.result.submissionStatus.task); // Attach the table to the page const taskTableContainer = document.querySelector('.page-section__container'); const container = document.createElement('div'); container.style.overflowX = 'auto'; container.style.padding = '15px'; container.appendChild(createTable(result.data.response.result.submissionStatus.task)); // Insert the container as the first child of taskTableContainer const firstChild = taskTableContainer.firstChild; taskTableContainer.insertBefore(container, firstChild); // 监听结束后重新启动 startListening(); }) .catch(error => { console.error('监听错误:', error.message); // 出错后也尝试重新启动 startListening(); }); } startListening(); })();