Greasy Fork 支持简体中文。

Python123

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

// ==UserScript==
// @name         Python123
// @namespace    http://tampermonkey.net/
// @version      0.2
// @description  一个用于获取 Python123 选择题解析或答案的,接入大模型完成程序设计题的脚本
// @author       forxk
// @match        *://python123.io/*
// @grant        none
// @license MIT
// ==/UserScript==

(function () {
  'use strict';

  // 创建一个显示数据包的窗体
  const packetContainer = document.createElement('div');
  packetContainer.style.position = 'fixed';
  packetContainer.style.top = '10px';
  packetContainer.style.right = '10px';
  packetContainer.style.width = '350px';
  packetContainer.style.height = '500px';
  packetContainer.style.overflowY = 'scroll';
  packetContainer.style.backgroundColor = '#f9f9f9';
  packetContainer.style.border = '1px solid #ccc';
  packetContainer.style.borderRadius = '8px';
  packetContainer.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.1)';
  packetContainer.style.zIndex = '9999';
  packetContainer.style.fontSize = '14px';
  packetContainer.style.resize = 'both'; // 允许拖动四周边框调整大小
  packetContainer.style.overflow = 'auto';
  packetContainer.style.minWidth = '200px'; // 设置最小宽度
  packetContainer.style.minHeight = '200px'; // 设置最小高度
  document.body.appendChild(packetContainer);

  // 创建控制面板
  const controlPanel = document.createElement('div');
  controlPanel.style.position = 'sticky';
  controlPanel.style.top = '0';
  controlPanel.style.backgroundColor = '#e0e0e0';
  controlPanel.style.borderBottom = '1px solid #ccc';
  controlPanel.style.width = '100%';
  controlPanel.style.display = 'flex';
  controlPanel.style.flexDirection = 'column';
  controlPanel.style.padding = '5px';
  controlPanel.style.borderTopLeftRadius = '8px';
  controlPanel.style.borderTopRightRadius = '8px';
  controlPanel.style.zIndex = '10000'; // 确保控制面板在最上层
  packetContainer.appendChild(controlPanel);

  // 创建按钮容器
  const buttonContainer = document.createElement('div');
  buttonContainer.style.display = 'flex';
  buttonContainer.style.justifyContent = 'space-between';
  buttonContainer.style.marginBottom = '5px';
  controlPanel.appendChild(buttonContainer);

  const moveButton = document.createElement('button');
  moveButton.textContent = 'Move';
  moveButton.style.marginRight = '5px';
  moveButton.style.padding = '5px 10px';
  moveButton.style.border = 'none';
  moveButton.style.borderRadius = '4px';
  moveButton.style.backgroundColor = 'rgb(96, 206, 179)';
  moveButton.style.color = 'white';
  moveButton.style.cursor = 'pointer';
  buttonContainer.appendChild(moveButton);

  const minimizeButton = document.createElement('button');
  minimizeButton.textContent = 'Minimize';
  minimizeButton.style.padding = '5px 10px';
  minimizeButton.style.border = 'none';
  minimizeButton.style.borderRadius = '4px';
  minimizeButton.style.backgroundColor = 'rgb(96, 206, 179)';
  minimizeButton.style.color = 'white';
  minimizeButton.style.cursor = 'pointer';
  buttonContainer.appendChild(minimizeButton);

  const toggleButton = document.createElement('button');
  toggleButton.textContent = 'Toggle';
  toggleButton.style.padding = '5px 10px';
  toggleButton.style.border = 'none';
  toggleButton.style.borderRadius = '4px';
  toggleButton.style.backgroundColor = 'rgb(96, 206, 179)';
  toggleButton.style.color = 'white';
  toggleButton.style.cursor = 'pointer';
  buttonContainer.appendChild(toggleButton);

  // 创建模型选择容器
  const modelContainer = document.createElement('div');
  modelContainer.style.display = 'flex';
  modelContainer.style.justifyContent = 'space-between';
  modelContainer.style.marginBottom = '5px';
  controlPanel.appendChild(modelContainer);

  const modelSelect = document.createElement('select');
  modelSelect.style.padding = '5px 10px';
  modelSelect.style.border = 'none';
  modelSelect.style.borderRadius = '4px';
  modelSelect.style.backgroundColor = 'rgb(96, 206, 179)';
  modelSelect.style.color = 'white';
  modelSelect.style.cursor = 'pointer';
  const models = [
    'THUDM/glm-4-9b-chat',
    'Qwen/Qwen2.5-72B-Instruct',
    'deepseek-ai/DeepSeek-V2.5',
    'deepseek-ai/DeepSeek-V2-Chat',
    'Qwen/Qwen2.5-7B-Instruct'
  ];
  models.forEach(model => {
    const option = document.createElement('option');
    option.value = model;
    option.textContent = model;
    modelSelect.appendChild(option);
  });
  modelContainer.appendChild(modelSelect);

  // 从 localStorage 中读取并设置模型选择
  const savedModel = localStorage.getItem('selectedModel');
  if (savedModel) {
    modelSelect.value = savedModel;
  }

  // 创建搜索框容器
  const searchContainer = document.createElement('div');
  searchContainer.style.display = 'flex';
  searchContainer.style.justifyContent = 'space-between';
  controlPanel.appendChild(searchContainer);

  // 创建搜索框
  const searchInput = document.createElement('input');
  searchInput.type = 'text';
  searchInput.placeholder = '搜索内容';
  searchInput.style.padding = '5px 10px';
  searchInput.style.border = 'none';
  searchInput.style.borderRadius = '4px';
  searchInput.style.flexGrow = '1';
  searchContainer.appendChild(searchInput);

  // 监听搜索框输入事件
  searchInput.addEventListener('input', function () {
    const searchText = searchInput.value.toLowerCase();
    const packets = packetContainer.querySelectorAll('div.packet');
    packets.forEach(packet => {
      const content = packet.textContent.toLowerCase();
      if (content.includes(searchText)) {
        packet.style.display = 'block';
      } else {
        packet.style.display = 'none';
      }
    });
  });

  // 创建API Key输入框和按钮
  const apiKeyContainer = document.createElement('div');
  apiKeyContainer.style.display = 'flex';
  apiKeyContainer.style.justifyContent = 'space-between';
  apiKeyContainer.style.marginBottom = '5px';
  controlPanel.appendChild(apiKeyContainer);

  const apiKeyInput = document.createElement('input');
  apiKeyInput.type = 'text';
  apiKeyInput.placeholder = 'Enter API Key';
  apiKeyInput.style.padding = '5px 10px';
  apiKeyInput.style.border = 'none';
  apiKeyInput.style.borderRadius = '4px';
  apiKeyInput.style.flexGrow = '1';
  apiKeyContainer.appendChild(apiKeyInput);

  const saveApiKeyButton = document.createElement('button');
  saveApiKeyButton.textContent = 'Save API Key';
  saveApiKeyButton.style.padding = '5px 10px';
  saveApiKeyButton.style.border = 'none';
  saveApiKeyButton.style.borderRadius = '4px';
  saveApiKeyButton.style.backgroundColor = 'rgb(96, 206, 179)';
  saveApiKeyButton.style.color = 'white';
  saveApiKeyButton.style.cursor = 'pointer';
  apiKeyContainer.appendChild(saveApiKeyButton);

  // 从 localStorage 中读取并设置API Key
  const savedApiKey = localStorage.getItem('apiKey');
  if (savedApiKey) {
    apiKeyInput.value = savedApiKey;
  }

  saveApiKeyButton.onclick = function () {
    const apiKey = apiKeyInput.value;
    localStorage.setItem('apiKey', apiKey);
    alert('API Key saved!');
  };

  let isMinimized = false;
  minimizeButton.onclick = function () {
    if (isMinimized) {
      packetContainer.style.height = '500px';
      minimizeButton.textContent = 'Minimize';
    } else {
      packetContainer.style.height = '30px';
      minimizeButton.textContent = 'Maximize';
    }
    isMinimized = !isMinimized;
  };

  toggleButton.onclick = function () {
    if (packetContainer.style.display === 'none') {
      packetContainer.style.display = 'block';
    } else {
      packetContainer.style.display = 'none';
    }
  };

  modelSelect.onchange = function () {
    localStorage.setItem('selectedModel', modelSelect.value);
    refreshData();
  };

  // 使窗口可通过拖动Move按钮移动
  moveButton.onmousedown = function (event) {
    event.preventDefault();
    let shiftX = event.clientX - packetContainer.getBoundingClientRect().left;
    let shiftY = event.clientY - packetContainer.getBoundingClientRect().top;

    function moveAt(pageX, pageY) {
      packetContainer.style.left = pageX - shiftX + 'px';
      packetContainer.style.top = pageY - shiftY + 'px';
    }

    function onMouseMove(event) {
      moveAt(event.pageX, event.pageY);
    }

    document.addEventListener('mousemove', onMouseMove);

    document.onmouseup = function () {
      document.removeEventListener('mousemove', onMouseMove);
      document.onmouseup = null;
    };
  };

  moveButton.ondragstart = function () {
    return false;
  };

  function displayPacket(name, content, explanationContent, answer, description, aiResponse, type) {
    const packetElement = document.createElement('div');
    packetElement.classList.add('packet');
    packetElement.style.marginBottom = '10px';
    packetElement.style.borderBottom = '1px solid #ccc';
    packetElement.style.paddingBottom = '10px';
    packetElement.style.padding = '10px';
    packetElement.style.backgroundColor = 'white';
    packetElement.style.borderRadius = '4px';
    packetElement.style.boxShadow = '0 2px 4px rgba(0, 0, 0, 0.1)';

    const nameElement = document.createElement('div');
    nameElement.textContent = `Name: ${name}`;
    nameElement.style.fontWeight = 'bold';
    nameElement.style.marginBottom = '5px';
    packetElement.appendChild(nameElement);

    if (content) {
      const contentElement = document.createElement('div');
      contentElement.textContent = `Content: ${content}`;
      contentElement.style.marginBottom = '5px';
      packetElement.appendChild(contentElement);
    }

    if (explanationContent) {
      const explanationElement = document.createElement('div');
      explanationElement.textContent = `Explanation: ${explanationContent}`;
      explanationElement.style.marginBottom = '5px';
      packetElement.appendChild(explanationElement);
    }

    if (answer) {
      const answerElement = document.createElement('div');
      answerElement.textContent = `Answer: ${answer}`;
      answerElement.style.marginBottom = '5px';
      packetElement.appendChild(answerElement);
    }

    if (description) {
      const descriptionElement = document.createElement('div');
      descriptionElement.textContent = `Description: ${description}`;
      descriptionElement.style.marginBottom = '5px';
      packetElement.appendChild(descriptionElement);
    }

    if (type) {
      const typeElement = document.createElement('div');
      typeElement.textContent = `Type: ${type}`;
      typeElement.style.marginBottom = '5px';
      packetElement.appendChild(typeElement);
    }

    if (aiResponse) {
      const aiResponseElement = document.createElement('pre');
      aiResponseElement.style.marginTop = '10px';
      aiResponseElement.style.padding = '10px';
      aiResponseElement.style.backgroundColor = '#f0f0f0';
      aiResponseElement.style.borderRadius = '4px';
      aiResponseElement.textContent = aiResponse;
      packetElement.appendChild(aiResponseElement);
    }

    packetContainer.appendChild(packetElement);
  }

  function extractAndDisplayData(json) {
    json.data.forEach(item => {
      const name = item.name || 'N/A';
      const content = item.content ? stripHtml(item.content) : 'N/A';
      const explanationContent = item.explanation_content ? stripHtml(item.explanation_content) : null;
      const answer = item.answer || null;
      const description = item.description || null;
      const type = item.type || 'N/A'; // 解析出 type 类型

      // 检查是否能从Content中找到答案
      let answerText = null;
      if (answer && content) {
        try {
          const contentArray = JSON.parse(content);
          answerText = contentArray.find(item => item[0] == answer)[1];
        } catch (e) {
          console.error('Failed to parse content or find answer:', e);
        }
      }

      if (answerText) {
        displayPacket(name, content, explanationContent, answer, description, null, type);
      } else {
        const apiKey = localStorage.getItem('apiKey');
        if (!apiKey) {
          displayPacket(name, content, explanationContent, answer, description, 'API Key is missing, cannot call AI model.', type);
        } else {
          sendToAI(name, content, explanationContent, answer, description, type, (aiResponse) => {
            displayPacket(name, content, explanationContent, answer, description, aiResponse, type);
          });
        }
      }
    });
  }

  function stripHtml(html) {
    const tempDiv = document.createElement('div');
    tempDiv.innerHTML = html;
    return tempDiv.textContent || tempDiv.innerText || '';
  }

  function sendToAI(name, content, explanationContent, answer, description, type, callback) {
    const apiKey = localStorage.getItem('apiKey');
    if (!apiKey) {
      alert('Please enter your API Key.');
      return;
    }

    const prompt = `Name: ${name}\nContent: ${content}\nExplanation: ${explanationContent}\nAnswer: ${answer}\nDescription: ${description}\nQuestion Type: ${type}`;
    const options = {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${apiKey}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        model: modelSelect.value,
        messages: [
          { role: 'system', content: '你现在是一个 Python 程序助理,下面我将向你输入题目,题目的类型由“Question Type”决定,如果是“Choice”请输出完整选项;如果是"programming",请你按照要求输出Python代码,请不要输出md格式的代码,而是直接输出,并且输出完整的注释,以便我能直接复制粘贴' },
          { role: 'user', content: prompt }
        ]
      })
    };

    fetch('https://api.siliconflow.cn/v1/chat/completions', options)
      .then(response => response.json())
      .then(response => {
        console.log('AI Response:', response);
        const responseContent = response.choices && response.choices[0] && response.choices[0].message && response.choices[0].message.content;
        callback(responseContent || 'No response');
      })
      .catch(err => {
        console.error('AI Request Error:', err);
        callback('Error fetching AI response');
      });
  }

  function refreshData() {
    // 重新获取数据并刷新显示
    const event = new Event('keydown');
    event.ctrlKey = true;
    event.key = 'q';
    document.dispatchEvent(event);
  }

  // 正则表达式匹配URL
  const urlPattern = /\/api\/v1\/student\/courses\/\d+\/groups\/\d+\/problems/;

  // 拦截XMLHttpRequest
  (function (open) {
    XMLHttpRequest.prototype.open = function (method, url, async, user, password) {
      console.log('XMLHttpRequest open called with URL:', url);
      this.addEventListener('readystatechange', function () {
        if (this.readyState === 4) {
          console.log('XMLHttpRequest readyState:', this.readyState, 'status:', this.status, 'URL:', url);
          if (this.status === 200 && urlPattern.test(url)) {
            console.log('Intercepted Prombles packet:', this.responseText);
            try {
              const jsonResponse = JSON.parse(this.responseText);
              extractAndDisplayData(jsonResponse);
            } catch (e) {
              console.error('Failed to parse JSON response:', e);
            }
          }
        }
      }, false);
      open.call(this, method, url, async, user, password);
    };
  })(XMLHttpRequest.prototype.open);

  // 拦截Fetch API
  (function (fetch) {
    window.fetch = function () {
      console.log('Fetch called with arguments:', arguments);
      return fetch.apply(this, arguments).then(function (response) {
        console.log('Fetch response URL:', response.url);
        if (urlPattern.test(response.url)) {
          response.clone().text().then(function (text) {
            console.log('Intercepted Prombles packet:', text);
            try {
              const jsonResponse = JSON.parse(text);
              extractAndDisplayData(jsonResponse);
            } catch (e) {
              console.error('Failed to parse JSON response:', e);
            }
          });
        }
        return response;
      });
    };
  })(window.fetch);

  // 添加快捷键监听器
  document.addEventListener('keydown', function (event) {
    if (event.ctrlKey && event.key === 'p') {
      packetContainer.style.display = 'none';
    }
    if (event.ctrlKey && event.key === 'q') {
      packetContainer.style.display = 'block';
    }
  });
})();