Greasy Fork 支持简体中文。

Python123

一个用于获取 Python123 选择题解析或答案的,接入大模型完成程序设计题的脚本

  1. // ==UserScript==
  2. // @name Python123
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.2
  5. // @description 一个用于获取 Python123 选择题解析或答案的,接入大模型完成程序设计题的脚本
  6. // @author forxk
  7. // @match *://python123.io/*
  8. // @grant none
  9. // @license MIT
  10. // ==/UserScript==
  11.  
  12. (function () {
  13. 'use strict';
  14.  
  15. // 创建一个显示数据包的窗体
  16. const packetContainer = document.createElement('div');
  17. packetContainer.style.position = 'fixed';
  18. packetContainer.style.top = '10px';
  19. packetContainer.style.right = '10px';
  20. packetContainer.style.width = '350px';
  21. packetContainer.style.height = '500px';
  22. packetContainer.style.overflowY = 'scroll';
  23. packetContainer.style.backgroundColor = '#f9f9f9';
  24. packetContainer.style.border = '1px solid #ccc';
  25. packetContainer.style.borderRadius = '8px';
  26. packetContainer.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.1)';
  27. packetContainer.style.zIndex = '9999';
  28. packetContainer.style.fontSize = '14px';
  29. packetContainer.style.resize = 'both'; // 允许拖动四周边框调整大小
  30. packetContainer.style.overflow = 'auto';
  31. packetContainer.style.minWidth = '200px'; // 设置最小宽度
  32. packetContainer.style.minHeight = '200px'; // 设置最小高度
  33. document.body.appendChild(packetContainer);
  34.  
  35. // 创建控制面板
  36. const controlPanel = document.createElement('div');
  37. controlPanel.style.position = 'sticky';
  38. controlPanel.style.top = '0';
  39. controlPanel.style.backgroundColor = '#e0e0e0';
  40. controlPanel.style.borderBottom = '1px solid #ccc';
  41. controlPanel.style.width = '100%';
  42. controlPanel.style.display = 'flex';
  43. controlPanel.style.flexDirection = 'column';
  44. controlPanel.style.padding = '5px';
  45. controlPanel.style.borderTopLeftRadius = '8px';
  46. controlPanel.style.borderTopRightRadius = '8px';
  47. controlPanel.style.zIndex = '10000'; // 确保控制面板在最上层
  48. packetContainer.appendChild(controlPanel);
  49.  
  50. // 创建按钮容器
  51. const buttonContainer = document.createElement('div');
  52. buttonContainer.style.display = 'flex';
  53. buttonContainer.style.justifyContent = 'space-between';
  54. buttonContainer.style.marginBottom = '5px';
  55. controlPanel.appendChild(buttonContainer);
  56.  
  57. const moveButton = document.createElement('button');
  58. moveButton.textContent = 'Move';
  59. moveButton.style.marginRight = '5px';
  60. moveButton.style.padding = '5px 10px';
  61. moveButton.style.border = 'none';
  62. moveButton.style.borderRadius = '4px';
  63. moveButton.style.backgroundColor = 'rgb(96, 206, 179)';
  64. moveButton.style.color = 'white';
  65. moveButton.style.cursor = 'pointer';
  66. buttonContainer.appendChild(moveButton);
  67.  
  68. const minimizeButton = document.createElement('button');
  69. minimizeButton.textContent = 'Minimize';
  70. minimizeButton.style.padding = '5px 10px';
  71. minimizeButton.style.border = 'none';
  72. minimizeButton.style.borderRadius = '4px';
  73. minimizeButton.style.backgroundColor = 'rgb(96, 206, 179)';
  74. minimizeButton.style.color = 'white';
  75. minimizeButton.style.cursor = 'pointer';
  76. buttonContainer.appendChild(minimizeButton);
  77.  
  78. const toggleButton = document.createElement('button');
  79. toggleButton.textContent = 'Toggle';
  80. toggleButton.style.padding = '5px 10px';
  81. toggleButton.style.border = 'none';
  82. toggleButton.style.borderRadius = '4px';
  83. toggleButton.style.backgroundColor = 'rgb(96, 206, 179)';
  84. toggleButton.style.color = 'white';
  85. toggleButton.style.cursor = 'pointer';
  86. buttonContainer.appendChild(toggleButton);
  87.  
  88. // 创建模型选择容器
  89. const modelContainer = document.createElement('div');
  90. modelContainer.style.display = 'flex';
  91. modelContainer.style.justifyContent = 'space-between';
  92. modelContainer.style.marginBottom = '5px';
  93. controlPanel.appendChild(modelContainer);
  94.  
  95. const modelSelect = document.createElement('select');
  96. modelSelect.style.padding = '5px 10px';
  97. modelSelect.style.border = 'none';
  98. modelSelect.style.borderRadius = '4px';
  99. modelSelect.style.backgroundColor = 'rgb(96, 206, 179)';
  100. modelSelect.style.color = 'white';
  101. modelSelect.style.cursor = 'pointer';
  102. const models = [
  103. 'THUDM/glm-4-9b-chat',
  104. 'Qwen/Qwen2.5-72B-Instruct',
  105. 'deepseek-ai/DeepSeek-V2.5',
  106. 'deepseek-ai/DeepSeek-V2-Chat',
  107. 'Qwen/Qwen2.5-7B-Instruct'
  108. ];
  109. models.forEach(model => {
  110. const option = document.createElement('option');
  111. option.value = model;
  112. option.textContent = model;
  113. modelSelect.appendChild(option);
  114. });
  115. modelContainer.appendChild(modelSelect);
  116.  
  117. // 从 localStorage 中读取并设置模型选择
  118. const savedModel = localStorage.getItem('selectedModel');
  119. if (savedModel) {
  120. modelSelect.value = savedModel;
  121. }
  122.  
  123. // 创建搜索框容器
  124. const searchContainer = document.createElement('div');
  125. searchContainer.style.display = 'flex';
  126. searchContainer.style.justifyContent = 'space-between';
  127. controlPanel.appendChild(searchContainer);
  128.  
  129. // 创建搜索框
  130. const searchInput = document.createElement('input');
  131. searchInput.type = 'text';
  132. searchInput.placeholder = '搜索内容';
  133. searchInput.style.padding = '5px 10px';
  134. searchInput.style.border = 'none';
  135. searchInput.style.borderRadius = '4px';
  136. searchInput.style.flexGrow = '1';
  137. searchContainer.appendChild(searchInput);
  138.  
  139. // 监听搜索框输入事件
  140. searchInput.addEventListener('input', function () {
  141. const searchText = searchInput.value.toLowerCase();
  142. const packets = packetContainer.querySelectorAll('div.packet');
  143. packets.forEach(packet => {
  144. const content = packet.textContent.toLowerCase();
  145. if (content.includes(searchText)) {
  146. packet.style.display = 'block';
  147. } else {
  148. packet.style.display = 'none';
  149. }
  150. });
  151. });
  152.  
  153. // 创建API Key输入框和按钮
  154. const apiKeyContainer = document.createElement('div');
  155. apiKeyContainer.style.display = 'flex';
  156. apiKeyContainer.style.justifyContent = 'space-between';
  157. apiKeyContainer.style.marginBottom = '5px';
  158. controlPanel.appendChild(apiKeyContainer);
  159.  
  160. const apiKeyInput = document.createElement('input');
  161. apiKeyInput.type = 'text';
  162. apiKeyInput.placeholder = 'Enter API Key';
  163. apiKeyInput.style.padding = '5px 10px';
  164. apiKeyInput.style.border = 'none';
  165. apiKeyInput.style.borderRadius = '4px';
  166. apiKeyInput.style.flexGrow = '1';
  167. apiKeyContainer.appendChild(apiKeyInput);
  168.  
  169. const saveApiKeyButton = document.createElement('button');
  170. saveApiKeyButton.textContent = 'Save API Key';
  171. saveApiKeyButton.style.padding = '5px 10px';
  172. saveApiKeyButton.style.border = 'none';
  173. saveApiKeyButton.style.borderRadius = '4px';
  174. saveApiKeyButton.style.backgroundColor = 'rgb(96, 206, 179)';
  175. saveApiKeyButton.style.color = 'white';
  176. saveApiKeyButton.style.cursor = 'pointer';
  177. apiKeyContainer.appendChild(saveApiKeyButton);
  178.  
  179. // 从 localStorage 中读取并设置API Key
  180. const savedApiKey = localStorage.getItem('apiKey');
  181. if (savedApiKey) {
  182. apiKeyInput.value = savedApiKey;
  183. }
  184.  
  185. saveApiKeyButton.onclick = function () {
  186. const apiKey = apiKeyInput.value;
  187. localStorage.setItem('apiKey', apiKey);
  188. alert('API Key saved!');
  189. };
  190.  
  191. let isMinimized = false;
  192. minimizeButton.onclick = function () {
  193. if (isMinimized) {
  194. packetContainer.style.height = '500px';
  195. minimizeButton.textContent = 'Minimize';
  196. } else {
  197. packetContainer.style.height = '30px';
  198. minimizeButton.textContent = 'Maximize';
  199. }
  200. isMinimized = !isMinimized;
  201. };
  202.  
  203. toggleButton.onclick = function () {
  204. if (packetContainer.style.display === 'none') {
  205. packetContainer.style.display = 'block';
  206. } else {
  207. packetContainer.style.display = 'none';
  208. }
  209. };
  210.  
  211. modelSelect.onchange = function () {
  212. localStorage.setItem('selectedModel', modelSelect.value);
  213. refreshData();
  214. };
  215.  
  216. // 使窗口可通过拖动Move按钮移动
  217. moveButton.onmousedown = function (event) {
  218. event.preventDefault();
  219. let shiftX = event.clientX - packetContainer.getBoundingClientRect().left;
  220. let shiftY = event.clientY - packetContainer.getBoundingClientRect().top;
  221.  
  222. function moveAt(pageX, pageY) {
  223. packetContainer.style.left = pageX - shiftX + 'px';
  224. packetContainer.style.top = pageY - shiftY + 'px';
  225. }
  226.  
  227. function onMouseMove(event) {
  228. moveAt(event.pageX, event.pageY);
  229. }
  230.  
  231. document.addEventListener('mousemove', onMouseMove);
  232.  
  233. document.onmouseup = function () {
  234. document.removeEventListener('mousemove', onMouseMove);
  235. document.onmouseup = null;
  236. };
  237. };
  238.  
  239. moveButton.ondragstart = function () {
  240. return false;
  241. };
  242.  
  243. function displayPacket(name, content, explanationContent, answer, description, aiResponse, type) {
  244. const packetElement = document.createElement('div');
  245. packetElement.classList.add('packet');
  246. packetElement.style.marginBottom = '10px';
  247. packetElement.style.borderBottom = '1px solid #ccc';
  248. packetElement.style.paddingBottom = '10px';
  249. packetElement.style.padding = '10px';
  250. packetElement.style.backgroundColor = 'white';
  251. packetElement.style.borderRadius = '4px';
  252. packetElement.style.boxShadow = '0 2px 4px rgba(0, 0, 0, 0.1)';
  253.  
  254. const nameElement = document.createElement('div');
  255. nameElement.textContent = `Name: ${name}`;
  256. nameElement.style.fontWeight = 'bold';
  257. nameElement.style.marginBottom = '5px';
  258. packetElement.appendChild(nameElement);
  259.  
  260. if (content) {
  261. const contentElement = document.createElement('div');
  262. contentElement.textContent = `Content: ${content}`;
  263. contentElement.style.marginBottom = '5px';
  264. packetElement.appendChild(contentElement);
  265. }
  266.  
  267. if (explanationContent) {
  268. const explanationElement = document.createElement('div');
  269. explanationElement.textContent = `Explanation: ${explanationContent}`;
  270. explanationElement.style.marginBottom = '5px';
  271. packetElement.appendChild(explanationElement);
  272. }
  273.  
  274. if (answer) {
  275. const answerElement = document.createElement('div');
  276. answerElement.textContent = `Answer: ${answer}`;
  277. answerElement.style.marginBottom = '5px';
  278. packetElement.appendChild(answerElement);
  279. }
  280.  
  281. if (description) {
  282. const descriptionElement = document.createElement('div');
  283. descriptionElement.textContent = `Description: ${description}`;
  284. descriptionElement.style.marginBottom = '5px';
  285. packetElement.appendChild(descriptionElement);
  286. }
  287.  
  288. if (type) {
  289. const typeElement = document.createElement('div');
  290. typeElement.textContent = `Type: ${type}`;
  291. typeElement.style.marginBottom = '5px';
  292. packetElement.appendChild(typeElement);
  293. }
  294.  
  295. if (aiResponse) {
  296. const aiResponseElement = document.createElement('pre');
  297. aiResponseElement.style.marginTop = '10px';
  298. aiResponseElement.style.padding = '10px';
  299. aiResponseElement.style.backgroundColor = '#f0f0f0';
  300. aiResponseElement.style.borderRadius = '4px';
  301. aiResponseElement.textContent = aiResponse;
  302. packetElement.appendChild(aiResponseElement);
  303. }
  304.  
  305. packetContainer.appendChild(packetElement);
  306. }
  307.  
  308. function extractAndDisplayData(json) {
  309. json.data.forEach(item => {
  310. const name = item.name || 'N/A';
  311. const content = item.content ? stripHtml(item.content) : 'N/A';
  312. const explanationContent = item.explanation_content ? stripHtml(item.explanation_content) : null;
  313. const answer = item.answer || null;
  314. const description = item.description || null;
  315. const type = item.type || 'N/A'; // 解析出 type 类型
  316.  
  317. // 检查是否能从Content中找到答案
  318. let answerText = null;
  319. if (answer && content) {
  320. try {
  321. const contentArray = JSON.parse(content);
  322. answerText = contentArray.find(item => item[0] == answer)[1];
  323. } catch (e) {
  324. console.error('Failed to parse content or find answer:', e);
  325. }
  326. }
  327.  
  328. if (answerText) {
  329. displayPacket(name, content, explanationContent, answer, description, null, type);
  330. } else {
  331. const apiKey = localStorage.getItem('apiKey');
  332. if (!apiKey) {
  333. displayPacket(name, content, explanationContent, answer, description, 'API Key is missing, cannot call AI model.', type);
  334. } else {
  335. sendToAI(name, content, explanationContent, answer, description, type, (aiResponse) => {
  336. displayPacket(name, content, explanationContent, answer, description, aiResponse, type);
  337. });
  338. }
  339. }
  340. });
  341. }
  342.  
  343. function stripHtml(html) {
  344. const tempDiv = document.createElement('div');
  345. tempDiv.innerHTML = html;
  346. return tempDiv.textContent || tempDiv.innerText || '';
  347. }
  348.  
  349. function sendToAI(name, content, explanationContent, answer, description, type, callback) {
  350. const apiKey = localStorage.getItem('apiKey');
  351. if (!apiKey) {
  352. alert('Please enter your API Key.');
  353. return;
  354. }
  355.  
  356. const prompt = `Name: ${name}\nContent: ${content}\nExplanation: ${explanationContent}\nAnswer: ${answer}\nDescription: ${description}\nQuestion Type: ${type}`;
  357. const options = {
  358. method: 'POST',
  359. headers: {
  360. Authorization: `Bearer ${apiKey}`,
  361. 'Content-Type': 'application/json'
  362. },
  363. body: JSON.stringify({
  364. model: modelSelect.value,
  365. messages: [
  366. { role: 'system', content: '你现在是一个 Python 程序助理,下面我将向你输入题目,题目的类型由“Question Type”决定,如果是“Choice”请输出完整选项;如果是"programming",请你按照要求输出Python代码,请不要输出md格式的代码,而是直接输出,并且输出完整的注释,以便我能直接复制粘贴' },
  367. { role: 'user', content: prompt }
  368. ]
  369. })
  370. };
  371.  
  372. fetch('https://api.siliconflow.cn/v1/chat/completions', options)
  373. .then(response => response.json())
  374. .then(response => {
  375. console.log('AI Response:', response);
  376. const responseContent = response.choices && response.choices[0] && response.choices[0].message && response.choices[0].message.content;
  377. callback(responseContent || 'No response');
  378. })
  379. .catch(err => {
  380. console.error('AI Request Error:', err);
  381. callback('Error fetching AI response');
  382. });
  383. }
  384.  
  385. function refreshData() {
  386. // 重新获取数据并刷新显示
  387. const event = new Event('keydown');
  388. event.ctrlKey = true;
  389. event.key = 'q';
  390. document.dispatchEvent(event);
  391. }
  392.  
  393. // 正则表达式匹配URL
  394. const urlPattern = /\/api\/v1\/student\/courses\/\d+\/groups\/\d+\/problems/;
  395.  
  396. // 拦截XMLHttpRequest
  397. (function (open) {
  398. XMLHttpRequest.prototype.open = function (method, url, async, user, password) {
  399. console.log('XMLHttpRequest open called with URL:', url);
  400. this.addEventListener('readystatechange', function () {
  401. if (this.readyState === 4) {
  402. console.log('XMLHttpRequest readyState:', this.readyState, 'status:', this.status, 'URL:', url);
  403. if (this.status === 200 && urlPattern.test(url)) {
  404. console.log('Intercepted Prombles packet:', this.responseText);
  405. try {
  406. const jsonResponse = JSON.parse(this.responseText);
  407. extractAndDisplayData(jsonResponse);
  408. } catch (e) {
  409. console.error('Failed to parse JSON response:', e);
  410. }
  411. }
  412. }
  413. }, false);
  414. open.call(this, method, url, async, user, password);
  415. };
  416. })(XMLHttpRequest.prototype.open);
  417.  
  418. // 拦截Fetch API
  419. (function (fetch) {
  420. window.fetch = function () {
  421. console.log('Fetch called with arguments:', arguments);
  422. return fetch.apply(this, arguments).then(function (response) {
  423. console.log('Fetch response URL:', response.url);
  424. if (urlPattern.test(response.url)) {
  425. response.clone().text().then(function (text) {
  426. console.log('Intercepted Prombles packet:', text);
  427. try {
  428. const jsonResponse = JSON.parse(text);
  429. extractAndDisplayData(jsonResponse);
  430. } catch (e) {
  431. console.error('Failed to parse JSON response:', e);
  432. }
  433. });
  434. }
  435. return response;
  436. });
  437. };
  438. })(window.fetch);
  439.  
  440. // 添加快捷键监听器
  441. document.addEventListener('keydown', function (event) {
  442. if (event.ctrlKey && event.key === 'p') {
  443. packetContainer.style.display = 'none';
  444. }
  445. if (event.ctrlKey && event.key === 'q') {
  446. packetContainer.style.display = 'block';
  447. }
  448. });
  449. })();