// ==UserScript== // @name AtCoder Plus VScode CPH // @namespace http://tampermonkey.net/ // @version 5.4.3 // @author Andy_hpy & DeepSeek // @icon https://aowuucdn.oss-accelerate.aliyuncs.com/atcoder.png // @description 将AtCoder题目测试用例发送到VS Code的CPH插件 // @match https://atcoder.jp/contests/*/tasks/* // @grant GM_xmlhttpRequest // @grant GM_notification // @connect localhost // ==/UserScript== (function() { 'use strict'; // 等待页面加载完成 const wait = setInterval(() => { const place = document.querySelector('.nav.navbar-nav.navbar-right'); if (place) { clearInterval(wait); addButton(place); } }, 500); function addButton(place) { // 添加按钮 const cphBtn = document.createElement('button'); cphBtn.style.float = 'right'; cphBtn.style.height = '30px'; cphBtn.style.background = '#f72585'; cphBtn.style.color = 'white'; cphBtn.style.margin = '10px'; cphBtn.style.border = '1px solid #f72585'; cphBtn.className = 'cph btn'; cphBtn.href = 'javascript:void(0)'; cphBtn.innerHTML = '传送至CPH'; const testBtn = document.createElement('button'); testBtn.style.float = 'right'; testBtn.style.height = '30px'; testBtn.style.background = '#f72585'; testBtn.style.color = 'white'; testBtn.style.margin = '10px'; testBtn.style.border = '1px solid #f72585'; testBtn.className = 'cph btn'; testBtn.href = 'javascript:void(0)'; testBtn.innerHTML = '测试CPH连接'; // 按钮点击事件 cphBtn.addEventListener('click', function() { CPHClick(); }); testBtn.addEventListener('click', function() { testCPHClick(); }); // 添加至界面 place.appendChild(cphBtn); place.appendChild(testBtn); } function testCPHClick() { // 测试连接 GM_xmlhttpRequest({ method: 'GET', url: 'http://localhost:27121/', timeout: 2000, onload: function(response) { if (response.status === 200) { alert('连接成功'); } else { alert(`连接异常\n状态码: ${response.status}`); } }, onerror: function(error) { alert('连接失败\n无法连接到CPH插件 (端口:27121)'); }, ontimeout: function() { alert('连接超时\n请检查CPH插件是否运行'); } }); } function CPHClick() { // 解析题目信息 const pathParts = window.location.pathname.split('/'); const contestId = pathParts[2]; const problemId = pathParts[4]; // 转换为标准格式 const contestType = contestId.replace(/\d+/g, '').toUpperCase(); const contestNum = contestId.match(/\d+/)[0]; const problemCode = problemId.split('_').pop().toUpperCase(); // 获取时间限制 let timeLimit = 2000; // 默认2秒 const timeLimitElements = document.querySelectorAll('p'); timeLimitElements.forEach(el => { if (el.textContent.includes('Time Limit')) { const timeText = el.textContent; const match = timeText.match(/(\d+) sec/); if (match) { timeLimit = parseInt(match[1]) * 1000; // 转换为毫秒 } } }); // 提取测试用例 const tests = []; const sampleElements = document.querySelectorAll('.part'); sampleElements.forEach(el => { const header = el.querySelector('h3').textContent.trim(); if (header.includes('Sample Input') || header.includes('样本输入')) { const input = el.querySelector('pre').textContent.trim(); tests.push({ input, output: '' }); } if (header.includes('Sample Output') || header.includes('样本输出')) { const output = el.querySelector('pre').textContent.trim(); if (tests.length > 0) { tests[tests.length - 1].output = output; } } }); // 构建CPH兼容的JSON数据 const cphData = { name: `${contestType} ${contestNum} ${problemCode}`, group: `${contestType} ${contestNum}`, url: window.location.href, memoryLimit: 1024, // 默认内存限制 timeLimit: timeLimit + 1000, tests: tests, testType: "single", input: { type: "stdin" }, output: { type: "stdout" } }; // 发送到CPH插件 sendToCPH(cphData); } // 发送数据到CPH插件 function sendToCPH(data) { GM_xmlhttpRequest({ method: 'POST', url: 'http://localhost:27121/', headers: { 'Content-Type': 'application/json' }, data: JSON.stringify(data), onload: function(response) { if (response.status !== 200) { alert(`错误\nCPH插件返回错误: ${response.statusText || '无响应'}`); } }, onerror: function(error) { console.error('CPH连接错误:', error); var res = confirm('检测到vscode可能未打开,确定打开vscode?'); if (res) { location.href='vscode://'; alert('vscode已打开,请重新发送数据'); } } }); } })();