借助ai,写了个Genspark积分使用余量查看的油猴脚本

脚本功能
默认以悬浮窗的形式在页面右上角展示Genspark的积分使用量
脚本默认五分钟刷新一次,可以自己修改脚本刷新时间

因为是公司办公电脑的原因,截图有水印,就不给各位截图了
有热心佬友可以截图贴上来噢,谢谢了。

好用记得点赞哦

脚本内容

// ==UserScript==
// @name         Genspark积分余量
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  在浮动窗口中显示Genspark积分余量信息
// @author       LinuxDo 悟空
// @match        https://www.genspark.ai/*
// @grant        GM_xmlhttpRequest
// @connect      www.genspark.ai
// ==/UserScript==

;(() => {
  // 创建浮动窗口
  const floatingWindow = document.createElement("div")
  floatingWindow.id = "genspark-info-window"
  floatingWindow.style.cssText = `
  position: fixed;
  top: 20px;
  right: 20px;
  background-color: #ffffff;
  border: none;
  border-radius: 10px;
  padding: 12px;
  z-index: 9999;
  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
  width: 280px;
  opacity: 0.2;
  transition: all 0.3s ease;
  cursor: move;
  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
  font-size: 14px;
`

  // 创建浮动窗口的标题栏
  const header = document.createElement("div")
  header.style.cssText = `
  padding: 8px 10px;
  background-color: #f0f4f8;
  border-radius: 6px;
  margin-bottom: 12px;
  font-weight: bold;
  display: flex;
  justify-content: space-between;
  align-items: center;
  color: #2c3e50;
`
  header.textContent = "Genspark积分信息"

  // 创建刷新按钮
  const refreshButton = document.createElement("button")
  refreshButton.textContent = "↻"
  refreshButton.style.cssText = `
  background: none;
  border: none;
  cursor: pointer;
  font-size: 18px;
  color: #3498db;
  width: 24px;
  height: 24px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  transition: background-color 0.2s;
`

  refreshButton.addEventListener("mouseover", () => {
    refreshButton.style.backgroundColor = "#e8f4fc"
  })
  refreshButton.addEventListener("mouseout", () => {
    refreshButton.style.backgroundColor = "transparent"
  })

  header.appendChild(refreshButton)
  floatingWindow.appendChild(header)

  // 创建内容容器
  const content = document.createElement("div")
  content.id = "genspark-info-content"
  content.style.cssText = `
  display: none;
  line-height: 1.5;
  color: #333;
`
  floatingWindow.appendChild(content)

  // 将浮动窗口添加到文档中
  document.body.appendChild(floatingWindow)

  // 使浮动窗口可拖动
  let isDragging = false
  let offsetX, offsetY

  header.addEventListener("mousedown", (e) => {
    isDragging = true
    offsetX = e.clientX - floatingWindow.getBoundingClientRect().left
    offsetY = e.clientY - floatingWindow.getBoundingClientRect().top
  })

  document.addEventListener("mousemove", (e) => {
    if (isDragging) {
      floatingWindow.style.left = e.clientX - offsetX + "px"
      floatingWindow.style.top = e.clientY - offsetY + "px"
    }
  })

  document.addEventListener("mouseup", () => {
    isDragging = false
  })

  // 鼠标悬停时显示内容
  floatingWindow.addEventListener("mouseenter", () => {
    floatingWindow.style.opacity = "1"
    floatingWindow.style.transform = "scale(1.02)"
    content.style.display = "block"
  })

  floatingWindow.addEventListener("mouseleave", () => {
    floatingWindow.style.opacity = "0.2"
    floatingWindow.style.transform = "scale(1)"
    content.style.display = "none"
  })

  // 获取账户信息的函数
  function fetchAccountInfo() {
    const now = new Date()
    const endDate = new Date(now)
    const startDate = new Date(now)
    startDate.setMonth(startDate.getMonth() - 1)

    const startDateStr = startDate.toISOString()
    const endDateStr = endDate.toISOString()

    const url = `https://www.genspark.ai/api/payment/get_credit_history?start_date=${startDateStr}&end_date=${endDateStr}`

    GM_xmlhttpRequest({
      method: "GET",
      url: url,
      headers: {
        Accept: "*/*",
        "Cache-Control": "no-cache",
      },
      onload: (response) => {
        try {
          const data = JSON.parse(response.responseText)
          if (data.status === 0) {
            updateContent(data.data)
          } else {
            content.innerHTML = '<div style="color: red;">错误: ' + data.message + "</div>"
          }
        } catch (error) {
          content.innerHTML = '<div style="color: red;">解析响应时出错</div>'
          console.error("解析响应时出错:", error)
        }
      },
      onerror: (error) => {
        content.innerHTML = '<div style="color: red;">获取数据时出错</div>'
        console.error("获取数据时出错:", error)
      },
    })
  }

  // 更新内容的函数
  function updateContent(data) {
    // 格式化模型使用情况
    let modelUsageHtml = ""
    for (const model in data.model_usage) {
      modelUsageHtml += `<div style="display: flex; justify-content: space-between; margin: 3px 0;">
          <span style="color: #3498db;">${model}:</span>
          <span style="font-weight: bold;">${data.model_usage[model]}</span>
      </div>`
    }

    // 格式化查询时间段
    const startTime = new Date(data.query_period.start_time).toLocaleString()
    const endTime = new Date(data.query_period.end_time).toLocaleString()

    content.innerHTML = `
      <div style="margin-bottom: 12px; padding: 8px; background-color: #f8f9fa; border-radius: 6px;">
          <strong style="color: #2c3e50;">当前余额:</strong> <span style="color: #27ae60; font-weight: bold;">${data.current_balance}</span>
      </div>
      <div style="margin-bottom: 12px; padding: 8px; background-color: #f8f9fa; border-radius: 6px;">
          <strong style="color: #2c3e50;">套餐:</strong> <span style="text-transform: capitalize;">${data.plan}</span>
      </div>
      <div style="margin-bottom: 12px; padding: 8px; background-color: #f8f9fa; border-radius: 6px;">
          <strong style="color: #2c3e50;">模型使用情况:</strong>
          <div style="padding-left: 10px; margin-top: 5px;">${modelUsageHtml}</div>
      </div>
      <div style="padding: 8px; background-color: #f8f9fa; border-radius: 6px;">
          <strong style="color: #2c3e50;">查询时间段:</strong>
          <div style="padding-left: 10px; margin-top: 5px;">
              <div><span style="color: #7f8c8d;">开始:</span> ${startTime}</div>
              <div><span style="color: #7f8c8d;">结束:</span> ${endTime}</div>
          </div>
      </div>
  `
  }

  // 初始获取
  fetchAccountInfo()

  // 点击刷新按钮时刷新
  refreshButton.addEventListener("click", (e) => {
    e.stopPropagation()
    fetchAccountInfo()
  })

  // 每5分钟刷新一次
  setInterval(fetchAccountInfo, 5 * 60 * 1000)
})()

17 个赞

大佬太强了

1 个赞

太厉害,谢谢分享

谢谢佬友!

感谢大佬!

佬友真棒

F12看看报错内容是啥

// ==UserScript==
// @name         Genspark积分余量
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  在浮动窗口中显示Genspark积分余量信息
// @author       LinuxDo 悟空
// @match        https://www.genspark.ai/*
// @grant        GM_xmlhttpRequest
// @connect      www.genspark.ai
// ==/UserScript==

;(() => {
  // 创建浮动窗口
  const floatingWindow = document.createElement("div")
  floatingWindow.id = "genspark-info-window"
  floatingWindow.style.cssText = `
  position: fixed;
  top: 20px;
  right: 20px;
  background-color: #ffffff;
  border: none;
  border-radius: 10px;
  padding: 12px;
  z-index: 9999;
  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
  width: 280px;
  opacity: 0.2;
  transition: all 0.3s ease;
  cursor: move;
  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
  font-size: 14px;
`

  // 创建浮动窗口的标题栏
  const header = document.createElement("div")
  header.style.cssText = `
  padding: 8px 10px;
  background-color: #f0f4f8;
  border-radius: 6px;
  margin-bottom: 12px;
  font-weight: bold;
  display: flex;
  justify-content: space-between;
  align-items: center;
  color: #2c3e50;
`
  header.textContent = "Genspark积分信息"

  // 创建刷新按钮
  const refreshButton = document.createElement("button")
  refreshButton.textContent = "↻"
  refreshButton.style.cssText = `
  background: none;
  border: none;
  cursor: pointer;
  font-size: 18px;
  color: #3498db;
  width: 24px;
  height: 24px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  transition: background-color 0.2s;
`

  refreshButton.addEventListener("mouseover", () => {
    refreshButton.style.backgroundColor = "#e8f4fc"
  })
  refreshButton.addEventListener("mouseout", () => {
    refreshButton.style.backgroundColor = "transparent"
  })

  header.appendChild(refreshButton)
  floatingWindow.appendChild(header)

  // 创建内容容器
  const content = document.createElement("div")
  content.id = "genspark-info-content"
  content.style.cssText = `
  display: none;
  line-height: 1.5;
  color: #333;
`
  floatingWindow.appendChild(content)

  // 将浮动窗口添加到文档中
  document.body.appendChild(floatingWindow)

  // 使浮动窗口可拖动
  let isDragging = false
  let offsetX, offsetY

  header.addEventListener("mousedown", (e) => {
    isDragging = true
    offsetX = e.clientX - floatingWindow.getBoundingClientRect().left
    offsetY = e.clientY - floatingWindow.getBoundingClientRect().top
  })

  document.addEventListener("mousemove", (e) => {
    if (isDragging) {
      floatingWindow.style.left = e.clientX - offsetX + "px"
      floatingWindow.style.top = e.clientY - offsetY + "px"
    }
  })

  document.addEventListener("mouseup", () => {
    isDragging = false
  })

  // 鼠标悬停时显示内容
  floatingWindow.addEventListener("mouseenter", () => {
    floatingWindow.style.opacity = "1"
    floatingWindow.style.transform = "scale(1.02)"
    content.style.display = "block"
  })

  floatingWindow.addEventListener("mouseleave", () => {
    floatingWindow.style.opacity = "0.2"
    floatingWindow.style.transform = "scale(1)"
    content.style.display = "none"
  })

  // 获取账户信息的函数
  function fetchAccountInfo() {
    const now = new Date()
    const endDate = new Date(now)
    const startDate = new Date(now)
    startDate.setMonth(startDate.getMonth() - 1)

    const startDateStr = startDate.toISOString()
    const endDateStr = endDate.toISOString()

    const url = `https://www.genspark.ai/api/payment/get_credit_history?start_date=${startDateStr}&end_date=${endDateStr}`

    GM_xmlhttpRequest({
      method: "GET",
      url: url,
      headers: {
        Accept: "*/*",
        "Cache-Control": "no-cache",
      },
      onload: (response) => {
        try {
          const data = JSON.parse(response.responseText)
          console.log("API响应数据:", data) // 调试用,查看完整响应
          if (data.status === 0) {
            updateContent(data.data || {})
          } else {
            content.innerHTML = '<div style="color: red;">错误: ' + (data.message || "未知错误") + "</div>"
          }
        } catch (error) {
          content.innerHTML = '<div style="color: red;">解析响应时出错</div>'
          console.error("解析响应时出错:", error)
        }
      },
      onerror: (error) => {
        content.innerHTML = '<div style="color: red;">获取数据时出错</div>'
        console.error("获取数据时出错:", error)
      },
    })
  }

  // 更新内容的函数
  function updateContent(data) {
    // 格式化模型使用情况
    let modelUsageHtml = ""
    if (data.model_usage) {
      for (const model in data.model_usage) {
        modelUsageHtml += `<div style="display: flex; justify-content: space-between; margin: 3px 0;">
          <span style="color: #3498db;">${model}:</span>
          <span style="font-weight: bold;">${data.model_usage[model]}</span>
        </div>`
      }
    } else {
      modelUsageHtml = "<div>无模型使用数据</div>"
    }

    // 格式化查询时间段
    let periodHtml = ""
    if (data.query_period && data.query_period.start_time && data.query_period.end_time) {
      const startTime = new Date(data.query_period.start_time).toLocaleString()
      const endTime = new Date(data.query_period.end_time).toLocaleString()
      periodHtml = `
        <div style="padding: 8px; background-color: #f8f9fa; border-radius: 6px;">
          <strong style="color: #2c3e50;">查询时间段:</strong>
          <div style="padding-left: 10px; margin-top: 5px;">
            <div><span style="color: #7f8c8d;">开始:</span> ${startTime}</div>
            <div><span style="color: #7f8c8d;">结束:</span> ${endTime}</div>
          </div>
        </div>
      `
    }

    content.innerHTML = `
      <div style="margin-bottom: 12px; padding: 8px; background-color: #f8f9fa; border-radius: 6px;">
        <strong style="color: #2c3e50;">当前余额:</strong> <span style="color: #27ae60; font-weight: bold;">${data.current_balance !== undefined ? data.current_balance : "未知"}</span>
      </div>
      <div style="margin-bottom: 12px; padding: 8px; background-color: #f8f9fa; border-radius: 6px;">
        <strong style="color: #2c3e50;">套餐:</strong> <span style="text-transform: capitalize;">${data.plan || "未知"}</span>
      </div>
      <div style="margin-bottom: 12px; padding: 8px; background-color: #f8f9fa; border-radius: 6px;">
        <strong style="color: #2c3e50;">模型使用情况:</strong>
        <div style="padding-left: 10px; margin-top: 5px;">${modelUsageHtml}</div>
      </div>
      ${periodHtml}
    `
  }

  // 初始获取
  fetchAccountInfo()

  // 点击刷新按钮时刷新
  refreshButton.addEventListener("click", (e) => {
    e.stopPropagation()
    fetchAccountInfo()
    // 添加刷新动画
    refreshButton.style.transform = "rotate(360deg)"
    refreshButton.style.transition = "transform 0.5s"
    setTimeout(() => {
      refreshButton.style.transform = "rotate(0deg)"
      refreshButton.style.transition = "transform 0s"
    }, 500)
  })

  // 每5分钟刷新一次
  setInterval(fetchAccountInfo, 5 * 60 * 1000)
})()

佬友 试试这个,看下有没有问题
对了,为了定位问题我想问下你的是免费账号还是plus账号

我是免费账户

使用上面这个脚本之后 正常了吗,我这只有plus账号 所以不太方便查看你这个问题

已修复,感谢

有用有用,谢谢大佬

免费用户这200积分问个问题就归零了,感觉根本不需要关注积分问题。

站内好多佬都有plus会员来着的

在AI帮助下加了个可折叠的功能,佬看看

// ==UserScript==
// @name         Genspark积分余量 (可折叠版)
// @namespace    http://tampermonkey.net/
// @version      0.2
// @description  在浮动窗口中显示Genspark积分余量信息,可点击标题栏折叠/展开
// @author       LinuxDo 悟空 (优化建议 by Gemini)
// @match        https://www.genspark.ai/*
// @grant        GM_xmlhttpRequest
// @grant        GM_addStyle
// @connect      www.genspark.ai
// ==/UserScript==

;(() => {
  // --- CSS Styles ---
  GM_addStyle(`
    #genspark-info-window {
      position: fixed;
      top: 20px;
      right: 20px;
      background-color: #ffffff;
      border: none;
      border-radius: 10px;
      padding: 0;
      z-index: 9999;
      box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
      width: 280px;
      opacity: 0.2; /* Start semi-transparent */
      transition: opacity 0.3s ease, transform 0.3s ease; /* Transition opacity/scale on hover */
      font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
      font-size: 14px;
      overflow: hidden;
      cursor: default;
    }
    #genspark-info-window:hover {
       opacity: 1; /* Become fully opaque on hover */
       transform: scale(1.02); /* Slightly enlarge on hover */
       /* Content visibility is now controlled by click, not hover */
    }

    #genspark-info-header {
      padding: 8px 12px;
      background-color: #f0f4f8;
      /* border-radius: 6px 6px 0 0; */ /* Removed bottom rounding here */
      margin-bottom: 0;
      font-weight: bold;
      display: flex;
      justify-content: space-between;
      align-items: center;
      color: #2c3e50;
      cursor: pointer; /* Make header clickable for collapse/expand */
      position: relative; /* Needed for absolute positioned elements like indicators */
      border-bottom: 1px solid transparent; /* Prepare space for border when collapsed */
      transition: border-color 0.3s ease;
    }
    /* Style header when window is collapsed */
    #genspark-info-window.collapsed #genspark-info-header {
        border-bottom-left-radius: 6px; /* Round bottom corners when collapsed */
        border-bottom-right-radius: 6px;
        border-bottom: 1px solid #e0e4e8; /* Add a line when collapsed */
    }
     /* Optional: Add an indicator arrow */
    #genspark-info-header::after {
        content: '▲'; /* Up arrow when expanded */
        font-size: 10px;
        color: #7f8c8d;
        position: absolute;
        right: 35px; /* Position next to refresh button */
        top: 50%;
        transform: translateY(-50%);
        transition: transform 0.3s ease;
    }
    #genspark-info-window.collapsed #genspark-info-header::after {
        content: '▼'; /* Down arrow when collapsed */
        /* transform: translateY(-50%) rotate(180deg); */ /* Alternative way to flip */
    }


    #genspark-refresh-button {
      background: none;
      border: none;
      cursor: pointer;
      font-size: 18px;
      color: #3498db;
      width: 24px;
      height: 24px;
      display: flex;
      align-items: center;
      justify-content: center;
      border-radius: 50%;
      transition: background-color 0.2s;
      padding: 0;
      z-index: 1; /* Ensure button is clickable above header's ::after */
      position: relative; /* Ensure z-index works */
    }
    #genspark-refresh-button:hover {
      background-color: #e8f4fc;
    }

    #genspark-info-content {
      display: block; /* Always block, visibility controlled by max-height/opacity */
      padding: 12px;
      line-height: 1.5;
      color: #333;
      transition: max-height 0.35s ease-out, opacity 0.3s ease-in-out, padding 0.35s ease-out;
      max-height: 500px; /* Set a max-height sufficient for content */
      opacity: 1;
      overflow: hidden; /* Clip content that overflows during transition */
      background-color: #ffffff; /* Ensure content background */
      border-top: 1px solid #e0e4e8; /* Separator line */

    }
    /* Styles when collapsed */
    #genspark-info-window.collapsed #genspark-info-content {
        max-height: 0;
        opacity: 0;
        padding-top: 0;
        padding-bottom: 0;
        border-top-color: transparent; /* Hide border when collapsed */
    }

    /* Reuse styles from previous version */
    .info-section { margin-bottom: 12px; padding: 8px; background-color: #f8f9fa; border-radius: 6px; }
    .info-section:last-child { margin-bottom: 0; }
    .info-section strong { color: #2c3e50; }
    .info-section .highlight { color: #27ae60; font-weight: bold; }
    .info-section .plan { text-transform: capitalize; font-weight: bold; }
    .model-usage-item { display: flex; justify-content: space-between; margin: 3px 0; }
    .model-usage-item .model-name { color: #3498db; }
    .model-usage-item .model-count { font-weight: bold; }
    .query-period-details div { margin-top: 3px; }
    .query-period-details .label { color: #7f8c8d; display: inline-block; width: 40px; }
    .loading-message, .error-message { padding: 10px; text-align: center; color: #7f8c8d; }
    .error-message { color: red; font-weight: bold; }
  `);

  // --- Constants ---
  const WINDOW_ID = "genspark-info-window";
  const HEADER_ID = "genspark-info-header";
  const CONTENT_ID = "genspark-info-content";
  const REFRESH_BUTTON_ID = "genspark-refresh-button";
  const COLLAPSED_CLASS = "collapsed"; // CSS class for collapsed state
  const API_URL_BASE = "https://www.genspark.ai/api/payment/get_credit_history";
  const REFRESH_INTERVAL = 5 * 60 * 1000; // 5 minutes

  // --- DOM Elements ---
  const floatingWindow = document.createElement("div");
  floatingWindow.id = WINDOW_ID;
  // floatingWindow.classList.add(COLLAPSED_CLASS); // Start collapsed if desired

  const header = document.createElement("div");
  header.id = HEADER_ID;
  header.textContent = "Genspark积分信息"; // Text content set here

  const refreshButton = document.createElement("button");
  refreshButton.id = REFRESH_BUTTON_ID;
  refreshButton.textContent = "↻";
  refreshButton.title = "刷新";

  // Append refresh button inside the header, but before the text node if you want text first
  // Or append text first, then button - order matters for flexbox layout if not using specific order properties
  header.appendChild(refreshButton); // Appending button last aligns it right due to justify-content: space-between

  floatingWindow.appendChild(header);

  const content = document.createElement("div");
  content.id = CONTENT_ID;
  content.innerHTML = `<div class="loading-message">加载中...</div>`;
  floatingWindow.appendChild(content);

  document.body.appendChild(floatingWindow);

  // --- Collapse/Expand Logic ---
  header.addEventListener('click', (e) => {
      // IMPORTANT: Do not collapse/expand if the refresh button itself was clicked
      if (refreshButton.contains(e.target)) {
          return;
      }
      floatingWindow.classList.toggle(COLLAPSED_CLASS);
  });

  // --- Dragging Logic ---
  let isDragging = false;
  let offsetX, offsetY;
  let didDrag = false; // Flag to distinguish drag from click

  header.addEventListener("mousedown", (e) => {
    // Prevent dragging if clicking the refresh button
    if (refreshButton.contains(e.target)) return;

    isDragging = true;
    didDrag = false; // Reset drag flag
    offsetX = e.clientX - floatingWindow.getBoundingClientRect().left;
    offsetY = e.clientY - floatingWindow.getBoundingClientRect().top;
    // Add a small delay or movement threshold if needed to distinguish drag/click
    // but usually mouseup without movement registers as a click
    header.style.cursor = 'grabbing'; // Change cursor while dragging
  });

  document.addEventListener("mousemove", (e) => {
    if (isDragging) {
      // If mouse moved significantly, consider it a drag
      if (Math.abs(e.clientX - (floatingWindow.getBoundingClientRect().left + offsetX)) > 3 ||
          Math.abs(e.clientY - (floatingWindow.getBoundingClientRect().top + offsetY)) > 3) {
             didDrag = true;
      }
      floatingWindow.style.left = `${e.clientX - offsetX}px`;
      floatingWindow.style.top = `${e.clientY - offsetY}px`;
    }
  });

  document.addEventListener("mouseup", (e) => {
    if (isDragging) {
        isDragging = false;
        header.style.cursor = 'pointer'; // Restore cursor

        // If we dragged the mouse, prevent the click event on the header
        // which would trigger collapse/expand.
        if (didDrag) {
             e.stopPropagation(); // Stop the event from bubbling further (like to the click listener)
        }
    }
  });

   // Prevent the click listener on header if a drag happened
   // (This is slightly redundant with the didDrag flag in mouseup, but provides an extra layer)
   header.addEventListener('click', (e) => {
       if (didDrag) {
           e.stopPropagation();
           // console.log("Drag detected, preventing click toggle.");
       }
   }, true); // Use capture phase to potentially stop it earlier


  // --- API Call and Content Update --- (Functions remain the same as v0.2)
  function fetchAccountInfo() {
    content.innerHTML = `<div class="loading-message">加载中...</div>`;
    // No need to manage display/opacity here, CSS handles it based on 'collapsed' class

    const now = new Date();
    const endDate = new Date(now);
    const startDate = new Date(now);
    startDate.setMonth(startDate.getMonth() - 1);
    const startDateStr = startDate.toISOString();
    const endDateStr = endDate.toISOString();
    const url = `${API_URL_BASE}?start_date=${startDateStr}&end_date=${endDateStr}`;

    GM_xmlhttpRequest({
      method: "GET",
      url: url,
      headers: { Accept: "*/*", "Cache-Control": "no-cache" },
      onload: (response) => {
        try {
          const data = JSON.parse(response.responseText);
          if (data.status === 0 && data.data) {
            updateContent(data.data);
          } else {
            showError("错误: " + (data.message || "未能获取有效数据"));
          }
        } catch (error) {
          showError("解析响应时出错");
          console.error("解析响应时出错:", error, response.responseText);
        }
      },
      onerror: (error) => {
        showError("获取数据时出错");
        console.error("获取数据时出错:", error);
      },
    });
  }

  function showError(message) {
      content.innerHTML = `<div class="error-message">${message}</div>`;
       // Ensure window is expanded to show error message if it was collapsed
      // floatingWindow.classList.remove(COLLAPSED_CLASS); // Optional: uncomment to force expand on error
  }

  function formatModelUsage(modelUsage) {
    let html = '<div style="padding-left: 10px; margin-top: 5px;">';
    if (Object.keys(modelUsage).length === 0) { html += '无模型使用记录'; }
    else {
        for (const model in modelUsage) {
          html += `<div class="model-usage-item">
                      <span class="model-name">${model}:</span>
                      <span class="model-count">${modelUsage[model]}</span>
                   </div>`;
        }
    }
    html += '</div>';
    return html;
  }

  function formatQueryPeriod(queryPeriod) {
      if (!queryPeriod || !queryPeriod.start_time || !queryPeriod.end_time) { return '<div>查询时间段信息不可用</div>'; }
      const startTime = new Date(queryPeriod.start_time).toLocaleString();
      const endTime = new Date(queryPeriod.end_time).toLocaleString();
      return `<div class="query-period-details" style="padding-left: 10px; margin-top: 5px;">
                 <div><span class="label">开始:</span> ${startTime}</div>
                 <div><span class="label">结束:</span> ${endTime}</div>
              </div>`;
  }

  function updateContent(data) {
    const modelUsageHtml = formatModelUsage(data.model_usage || {});
    const queryPeriodHtml = formatQueryPeriod(data.query_period);
    const planDisplay = data.plan || '未知';
    const balance = data.current_balance !== undefined ? data.current_balance : 'N/A';

    content.innerHTML = `
      <div class="info-section">
          <strong>当前余额:</strong> <span class="highlight">${balance}</span>
      </div>
      <div class="info-section">
          <strong>套餐:</strong> <span class="plan">${planDisplay}</span>
      </div>
      <div class="info-section">
          <strong>模型使用情况:</strong>
          ${modelUsageHtml}
      </div>
      <div class="info-section">
          <strong>查询时间段:</strong>
          ${queryPeriodHtml}
      </div>
    `;
  }

  // --- Initial Fetch and Refresh Logic ---
  fetchAccountInfo(); // Initial fetch

  refreshButton.addEventListener("click", (e) => {
    e.stopPropagation(); // Prevent triggering header collapse/expand
    fetchAccountInfo();
  });

  setInterval(fetchAccountInfo, REFRESH_INTERVAL); // Periodic refresh

})();
1 个赞

让gpt补了一个悬停才显示

// ==UserScript==
// @name         Genspark积分余量
// @namespace    http://tampermonkey.net/
// @version      0.3
// @description  只在鼠标悬停右上角方框时显示的Genspark积分余量信息,可点击标题栏折叠/展开,带强制刷新按钮,每分钟自动刷新
// @author       LinuxDo 悟空
// @match        https://www.genspark.ai/*
// @grant        GM_xmlhttpRequest
// @grant        GM_addStyle
// @connect      www.genspark.ai
// ==/UserScript==

;(() => {
  // --- CSS Styles ---
  GM_addStyle(`
    #genspark-hover-box {
      position: fixed;
      top: 15px;
      right: 15px;
      width: 32px;
      height: 32px;
      border-radius: 8px;
      background: rgba(220,232,245,0.10);
      z-index: 9999;
      cursor: pointer;
      transition: background 0.15s;
      display: flex;
      align-items: center;
      justify-content: center;
      box-shadow: 0 2px 12px rgba(0,0,0,0.07);
    }
    #genspark-hover-box:hover {
      background: #e6f1fa;
    }
    #genspark-hover-dot {
      width: 12px;
      height: 12px;
      border-radius: 50%;
      background: #59a6f7;
      opacity: 0.5;
    }
    #genspark-info-window {
      position: fixed;
      top: 20px;
      right: 20px;
      background-color: #ffffff;
      border: none;
      border-radius: 10px;
      padding: 0;
      z-index: 10000;
      box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
      width: 280px;
      opacity: 1;
      transition: opacity 0.3s ease, transform 0.3s ease;
      font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
      font-size: 14px;
      overflow: hidden;
      cursor: default;
      display: none; /* 初始隐藏 */
    }
    #genspark-info-window.show { display: block; }
    #genspark-info-header {
      padding: 8px 12px;
      background-color: #f0f4f8;
      margin-bottom: 0;
      font-weight: bold;
      display: flex;
      justify-content: space-between;
      align-items: center;
      color: #2c3e50;
      cursor: pointer;
      position: relative;
      border-bottom: 1px solid transparent;
      transition: border-color 0.3s ease;
    }
    #genspark-info-window.collapsed #genspark-info-header {
        border-bottom-left-radius: 6px;
        border-bottom-right-radius: 6px;
        border-bottom: 1px solid #e0e4e8;
    }
    #genspark-info-header::after {
        content: '▲';
        font-size: 10px;
        color: #7f8c8d;
        position: absolute;
        right: 35px;
        top: 50%;
        transform: translateY(-50%);
        transition: transform 0.3s ease;
    }
    #genspark-info-window.collapsed #genspark-info-header::after {
        content: '▼';
    }
    #genspark-refresh-button {
      background: none;
      border: none;
      cursor: pointer;
      font-size: 18px;
      color: #3498db;
      width: 24px;
      height: 24px;
      display: flex;
      align-items: center;
      justify-content: center;
      border-radius: 50%;
      transition: background-color 0.2s;
      padding: 0;
      z-index: 1;
      position: relative;
    }
    #genspark-refresh-button:hover {
      background-color: #e8f4fc;
    }
    #genspark-info-content {
      display: block;
      padding: 12px;
      line-height: 1.5;
      color: #333;
      transition: max-height 0.35s ease-out, opacity 0.3s ease-in-out, padding 0.35s ease-out;
      max-height: 500px;
      opacity: 1;
      overflow: hidden;
      background-color: #ffffff;
      border-top: 1px solid #e0e4e8;
    }
    #genspark-info-window.collapsed #genspark-info-content {
        max-height: 0;
        opacity: 0;
        padding-top: 0;
        padding-bottom: 0;
        border-top-color: transparent;
    }
    .info-section { margin-bottom: 12px; padding: 8px; background-color: #f8f9fa; border-radius: 6px; }
    .info-section:last-child { margin-bottom: 0; }
    .info-section strong { color: #2c3e50; }
    .info-section .highlight { color: #27ae60; font-weight: bold; }
    .info-section .plan { text-transform: capitalize; font-weight: bold; }
    .model-usage-item { display: flex; justify-content: space-between; margin: 3px 0; }
    .model-usage-item .model-name { color: #3498db; }
    .model-usage-item .model-count { font-weight: bold; }
    .query-period-details div { margin-top: 3px; }
    .query-period-details .label { color: #7f8c8d; display: inline-block; width: 40px; }
    .loading-message, .error-message { padding: 10px; text-align: center; color: #7f8c8d; }
    .error-message { color: red; font-weight: bold; }
  `);

  // --- 常量 ---
  const WINDOW_ID = "genspark-info-window";
  const HEADER_ID = "genspark-info-header";
  const CONTENT_ID = "genspark-info-content";
  const REFRESH_BUTTON_ID = "genspark-refresh-button";
  const COLLAPSED_CLASS = "collapsed";
  const HOVER_BOX_ID = "genspark-hover-box";
  const HOVER_DOT_ID = "genspark-hover-dot";
  const API_URL_BASE = "https://www.genspark.ai/api/payment/get_credit_history";
  const REFRESH_INTERVAL = 1 * 60 * 1000; // 1分钟

  // --- 悬浮感应小方框 ---
  const hoverBox = document.createElement("div");
  hoverBox.id = HOVER_BOX_ID;
  hoverBox.title = "鼠标悬停显示Genspark积分余量";
  const hoverDot = document.createElement("div");
  hoverDot.id = HOVER_DOT_ID;
  hoverBox.appendChild(hoverDot);
  document.body.appendChild(hoverBox);

  // --- 主窗口 ---
  const floatingWindow = document.createElement("div");
  floatingWindow.id = WINDOW_ID;

  const header = document.createElement("div");
  header.id = HEADER_ID;
  header.textContent = "Genspark积分信息";
  const refreshButton = document.createElement("button");
  refreshButton.id = REFRESH_BUTTON_ID;
  refreshButton.textContent = "↻";
  refreshButton.title = "刷新";
  header.appendChild(refreshButton);
  floatingWindow.appendChild(header);
  const content = document.createElement("div");
  content.id = CONTENT_ID;
  content.innerHTML = `<div class="loading-message">加载中...</div>`;
  floatingWindow.appendChild(content);
  document.body.appendChild(floatingWindow);

  // --- 悬停控制显示/隐藏 ---
  let showTimer = null, hideTimer = null;
  hoverBox.addEventListener('mouseenter', () => {
    clearTimeout(hideTimer);
    showTimer = setTimeout(() => {
      floatingWindow.classList.add('show');
    }, 80);
  });
  hoverBox.addEventListener('mouseleave', () => {
    clearTimeout(showTimer);
    hideTimer = setTimeout(() => {
      floatingWindow.classList.remove('show');
    }, 200);
  });
  floatingWindow.addEventListener('mouseenter', () => {
    clearTimeout(hideTimer);
    floatingWindow.classList.add('show');
  });
  floatingWindow.addEventListener('mouseleave', () => {
    hideTimer = setTimeout(() => {
      floatingWindow.classList.remove('show');
    }, 200);
  });

  // --- 折叠/展开逻辑 ---
  header.addEventListener('click', (e) => {
      if (refreshButton.contains(e.target)) return;
      floatingWindow.classList.toggle(COLLAPSED_CLASS);
  });

  // --- 拖拽逻辑 ---
  let isDragging = false;
  let offsetX, offsetY;
  let didDrag = false;
  header.addEventListener("mousedown", (e) => {
    if (refreshButton.contains(e.target)) return;
    isDragging = true;
    didDrag = false;
    offsetX = e.clientX - floatingWindow.getBoundingClientRect().left;
    offsetY = e.clientY - floatingWindow.getBoundingClientRect().top;
    header.style.cursor = 'grabbing';
  });
  document.addEventListener("mousemove", (e) => {
    if (isDragging) {
      if (Math.abs(e.clientX - (floatingWindow.getBoundingClientRect().left + offsetX)) > 3 ||
          Math.abs(e.clientY - (floatingWindow.getBoundingClientRect().top + offsetY)) > 3) {
             didDrag = true;
      }
      floatingWindow.style.left = `${e.clientX - offsetX}px`;
      floatingWindow.style.top = `${e.clientY - offsetY}px`;
    }
  });
  document.addEventListener("mouseup", (e) => {
    if (isDragging) {
        isDragging = false;
        header.style.cursor = 'pointer';
        if (didDrag) {
             e.stopPropagation();
        }
    }
  });
  header.addEventListener('click', (e) => {
       if (didDrag) {
           e.stopPropagation();
       }
  }, true);

  // --- 数据请求与刷新 ---
  function fetchAccountInfo() {
    content.innerHTML = `<div class="loading-message">加载中...</div>`;
    const now = new Date();
    const endDate = new Date(now);
    const startDate = new Date(now);
    startDate.setMonth(startDate.getMonth() - 1);
    const startDateStr = startDate.toISOString();
    const endDateStr = endDate.toISOString();
    const url = `${API_URL_BASE}?start_date=${startDateStr}&end_date=${endDateStr}`;
    GM_xmlhttpRequest({
      method: "GET",
      url: url,
      headers: { Accept: "*/*", "Cache-Control": "no-cache" },
      onload: (response) => {
        try {
          const data = JSON.parse(response.responseText);
          if (data.status === 0 && data.data) {
            updateContent(data.data);
          } else {
            showError("错误: " + (data.message || "未能获取有效数据"));
          }
        } catch (error) {
          showError("解析响应时出错");
          console.error("解析响应时出错:", error, response.responseText);
        }
      },
      onerror: (error) => {
        showError("获取数据时出错");
        console.error("获取数据时出错:", error);
      },
    });
  }
  function showError(message) {
      content.innerHTML = `<div class="error-message">${message}</div>`;
  }
  function formatModelUsage(modelUsage) {
    let html = '<div style="padding-left: 10px; margin-top: 5px;">';
    if (Object.keys(modelUsage).length === 0) { html += '无模型使用记录'; }
    else {
        for (const model in modelUsage) {
          html += `<div class="model-usage-item">
                      <span class="model-name">${model}:</span>
                      <span class="model-count">${modelUsage[model]}</span>
                   </div>`;
        }
    }
    html += '</div>';
    return html;
  }
  function formatQueryPeriod(queryPeriod) {
      if (!queryPeriod || !queryPeriod.start_time || !queryPeriod.end_time) { return '<div>查询时间段信息不可用</div>'; }
      const startTime = new Date(queryPeriod.start_time).toLocaleString();
      const endTime = new Date(queryPeriod.end_time).toLocaleString();
      return `<div class="query-period-details" style="padding-left: 10px; margin-top: 5px;">
                 <div><span class="label">开始:</span> ${startTime}</div>
                 <div><span class="label">结束:</span> ${endTime}</div>
              </div>`;
  }
  function updateContent(data) {
    const modelUsageHtml = formatModelUsage(data.model_usage || {});
    const queryPeriodHtml = formatQueryPeriod(data.query_period);
    const planDisplay = data.plan || '未知';
    const balance = data.current_balance !== undefined ? data.current_balance : 'N/A';
    content.innerHTML = `
      <div class="info-section">
          <strong>当前余额:</strong> <span class="highlight">${balance}</span>
      </div>
      <div class="info-section">
          <strong>套餐:</strong> <span class="plan">${planDisplay}</span>
      </div>
      <div class="info-section">
          <strong>模型使用情况:</strong>
          ${modelUsageHtml}
      </div>
      <div class="info-section">
          <strong>查询时间段:</strong>
          ${queryPeriodHtml}
      </div>
    `;
  }
  // --- 初始化与定时刷新 ---
  fetchAccountInfo();
  refreshButton.addEventListener("click", (e) => {
    e.stopPropagation();
    fetchAccountInfo();
  });
  setInterval(fetchAccountInfo, REFRESH_INTERVAL);
})();
1 个赞

用上了用上了
跨时空的接力阿

让Claude改了一下,可以对比上次的查询的消耗大概。样式不完美的点在于按钮居中不了有点偏。

// ==UserScript==
// @name         Genspark积分余量
// @namespace    http://tampermonkey.net/
// @version      0.4
// @description  使用小图标显示Genspark积分余量信息,统计积分消耗
// @author       LinuxDo 悟空
// @match        https://www.genspark.ai/*
// @grant        GM_xmlhttpRequest
// @grant        GM_setValue
// @grant        GM_getValue
// @connect      www.genspark.ai
// ==/UserScript==

;(() => {
  // 创建容器元素来包含图标和信息窗口,方便一起移动
  const container = document.createElement("div")
  container.id = "genspark-container"
  container.style.cssText = `
    position: fixed !important;
    top: 50% !important;
    left: 50% !important;
    transform: translate(-50%, -50%) !important;
    z-index: 9999;
  `

  // 创建图标按钮
  const iconButton = document.createElement("div")
  iconButton.id = "genspark-info-icon"
  iconButton.style.cssText = `
    background-color: #3498db;
    border-radius: 50%;
    width: 40px;
    height: 40px;
    display: flex;
    align-items: center;
    justify-content: center;
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
    cursor: pointer;
    transition: all 0.3s ease;
  `

  // 添加图标
  iconButton.innerHTML = `
    <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
      <circle cx="12" cy="12" r="10"></circle>
      <line x1="12" y1="16" x2="12" y2="12"></line>
      <line x1="12" y1="8" x2="12" y2="8"></line>
    </svg>
  `

  // 创建信息窗口
  const infoWindow = document.createElement("div")
  infoWindow.id = "genspark-info-window"
  infoWindow.style.cssText = `
    position: absolute;
    top: 50px;
    right: 0;
    background-color: #ffffff;
    border: none;
    border-radius: 10px;
    padding: 12px;
    box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
    width: 280px;
    display: none;
    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
    font-size: 14px;
  `

  // 创建窗口的标题栏
  const header = document.createElement("div")
  header.style.cssText = `
    padding: 8px 10px;
    background-color: #f0f4f8;
    border-radius: 6px;
    margin-bottom: 12px;
    font-weight: bold;
    display: flex;
    justify-content: space-between;
    align-items: center;
    color: #2c3e50;
  `
  header.textContent = "Genspark积分信息"

  // 创建刷新按钮
  const refreshButton = document.createElement("button")
  refreshButton.textContent = "↻"
  refreshButton.style.cssText = `
    background: none;
    border: none;
    cursor: pointer;
    font-size: 18px;
    color: #3498db;
    width: 24px;
    height: 24px;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    transition: background-color 0.2s;
  `

  refreshButton.addEventListener("mouseover", () => {
    refreshButton.style.backgroundColor = "#e8f4fc"
  })
  refreshButton.addEventListener("mouseout", () => {
    refreshButton.style.backgroundColor = "transparent"
  })

  // 创建关闭按钮
  const closeButton = document.createElement("button")
  closeButton.textContent = "✕"
  closeButton.style.cssText = `
    background: none;
    border: none;
    cursor: pointer;
    font-size: 16px;
    color: #7f8c8d;
    margin-left: 10px;
    width: 24px;
    height: 24px;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    transition: background-color 0.2s;
  `

  closeButton.addEventListener("mouseover", () => {
    closeButton.style.backgroundColor = "#f3f3f3"
  })
  closeButton.addEventListener("mouseout", () => {
    closeButton.style.backgroundColor = "transparent"
  })

  // 按钮容器
  const buttonContainer = document.createElement("div")
  buttonContainer.style.display = "flex"
  buttonContainer.appendChild(refreshButton)
  buttonContainer.appendChild(closeButton)

  header.appendChild(buttonContainer)
  infoWindow.appendChild(header)

  // 创建内容容器
  const content = document.createElement("div")
  content.id = "genspark-info-content"
  content.style.cssText = `
    line-height: 1.5;
    color: #333;
  `
  infoWindow.appendChild(content)

  // 组装元素并添加到文档中
  container.appendChild(iconButton)
  container.appendChild(infoWindow)
  document.body.appendChild(container)

  // 点击图标显示信息窗口
  iconButton.addEventListener("click", () => {
    if (!isDragging) { // 只有在非拖拽状态下才执行点击操作
      if (infoWindow.style.display === "none" || infoWindow.style.display === "") {
        infoWindow.style.display = "block"
        fetchAccountInfo() // 点击时刷新数据
      } else {
        infoWindow.style.display = "none"
      }
    }
  })

  // 点击关闭按钮隐藏信息窗口
  closeButton.addEventListener("click", (e) => {
    e.stopPropagation()
    infoWindow.style.display = "none"
  })

  // 使整个容器可拖动
  let isDragging = false
  let offsetX, offsetY
  // 添加标志变量,跟踪用户是否手动移动过按钮
  let userHasMoved = GM_getValue("userHasMoved", false)

  iconButton.addEventListener("mousedown", (e) => {
    isDragging = true
    offsetX = e.clientX - container.getBoundingClientRect().left
    offsetY = e.clientY - container.getBoundingClientRect().top
    e.preventDefault() // 防止选中文本
  })

  document.addEventListener("mousemove", (e) => {
    if (isDragging) {
      container.style.left = e.clientX - offsetX + "px"
      container.style.top = e.clientY - offsetY + "px"
      // 重置right属性,避免冲突
      container.style.right = "auto"
      // 移除transform属性,避免影响定位
      container.style.transform = "none"
    }
  })

  document.addEventListener("mouseup", () => {
    if (isDragging) {
      isDragging = false
      // 设置用户已移动标志
      userHasMoved = true
      GM_setValue("userHasMoved", true)
      // 存储位置
      GM_setValue("buttonPositionX", container.style.left)
      GM_setValue("buttonPositionY", container.style.top)
    }
  })

  // 恢复上次保存的位置,但仅当用户手动移动过按钮时
  if (userHasMoved) {
    const savedX = GM_getValue("buttonPositionX", null)
    const savedY = GM_getValue("buttonPositionY", null)
    if (savedX && savedY) {
      container.style.left = savedX
      container.style.top = savedY
      container.style.right = "auto"
      container.style.transform = "none"
    }
  }

  // 阻止信息窗口内部点击事件冒泡到document
  infoWindow.addEventListener("click", (e) => {
    e.stopPropagation()
  })

  // 获取账户信息的函数
  function fetchAccountInfo() {
    // 显示加载状态
    content.innerHTML = '<div style="text-align: center; padding: 20px;">加载中...</div>'

    const now = new Date()
    const endDate = new Date(now)
    const startDate = new Date(now)
    startDate.setMonth(startDate.getMonth() - 1)

    const startDateStr = startDate.toISOString()
    const endDateStr = endDate.toISOString()

    const url = `https://www.genspark.ai/api/payment/get_credit_history?start_date=${startDateStr}&end_date=${endDateStr}`

    GM_xmlhttpRequest({
      method: "GET",
      url: url,
      headers: {
        Accept: "*/*",
        "Cache-Control": "no-cache",
      },
      onload: (response) => {
        try {
          const data = JSON.parse(response.responseText)
          if (data.status === 0) {
            // 获取当前余额和上次记录的信息
            const currentBalance = data.data.current_balance
            const previousBalance = GM_getValue("previousBalance", null)
            const lastUpdateTime = new Date(GM_getValue("lastUpdateTime", Date.now()))
            const previousSessionID = GM_getValue("sessionID", null)
            
            // 获取上次有效的积分变化信息
            const lastValidCreditChange = GM_getValue("lastValidCreditChange", {
              text: "暂无数据",
              color: "#7f8c8d",
              showTime: false
            })

            // 获取当前会话ID(使用页面加载时间作为标识)
            const currentSessionID = GM_getValue("currentSessionID", null)

            // 如果没有当前会话ID,则创建一个(第一次加载脚本)
            if (!currentSessionID) {
              const newSessionID = Date.now().toString()
              GM_setValue("currentSessionID", newSessionID)
            }

            let creditChangeInfo = lastValidCreditChange

            // 只有在以下情况下才更新余额和计算变化:
            // 1. 之前没有记录的余额(首次使用)
            // 2. 当前余额与之前记录的不同(确实发生了变化)
            if (previousBalance === null || currentBalance !== previousBalance) {
              // 计算消耗的积分
              if (previousBalance !== null) {
                const creditChange = currentBalance - previousBalance
                creditChangeInfo = {
                  text: creditChange >= 0 ? `+${creditChange}` : `${creditChange}`,
                  color: creditChange >= 0 ? "#27ae60" : "#e74c3c",
                  showTime: true
                }
                
                // 只有在实际发生积分变化时,才更新lastValidCreditChange
                GM_setValue("lastValidCreditChange", creditChangeInfo)
              }

              // 更新存储的值
              GM_setValue("previousBalance", currentBalance)
              GM_setValue("lastUpdateTime", Date.now())
              GM_setValue("sessionID", currentSessionID)
            }

            updateContent(data.data, creditChangeInfo, lastUpdateTime)
            // 更新图标显示当前余额
            updateIcon(data.data.current_balance)
          } else {
            content.innerHTML = '<div style="color: red; padding: 10px;">错误: ' + data.message + "</div>"
          }
        } catch (error) {
          content.innerHTML = '<div style="color: red; padding: 10px;">解析响应时出错</div>'
          console.error("解析响应时出错:", error)
        }
      },
      onerror: (error) => {
        content.innerHTML = '<div style="color: red; padding: 10px;">获取数据时出错</div>'
        console.error("获取数据时出错:", error)
      },
    })
  }

  // 更新图标显示
  function updateIcon(balance) {
    // 如果余额低于某个阈值,可以改变图标颜色
    if (balance < 1000) {
      iconButton.style.backgroundColor = "#e74c3c" // 红色警告
    } else if (balance < 5000) {
      iconButton.style.backgroundColor = "#f39c12" // 黄色警告
    } else {
      iconButton.style.backgroundColor = "#2ecc71" // 绿色正常
    }

    // 添加余额指示
    iconButton.setAttribute("title", `当前积分: ${balance}`)
  }

  // 更新内容的函数
  function updateContent(data, creditChangeInfo, lastUpdateTime) {
    // 格式化模型使用情况
    let modelUsageHtml = ""
    for (const model in data.model_usage) {
      modelUsageHtml += `<div style="display: flex; justify-content: space-between; margin: 3px 0;">
          <span style="color: #3498db;">${model}:</span>
          <span style="font-weight: bold;">${data.model_usage[model]}</span>
      </div>`
    }

    // 格式化查询时间段
    const startTime = new Date(data.query_period.start_time).toLocaleString()
    const endTime = new Date(data.query_period.end_time).toLocaleString()

    // 格式化最后更新时间
    const lastUpdateTimeStr = lastUpdateTime.toLocaleString()

    // 积分变化区块
    let creditChangeHtml = `
      <div style="margin-bottom: 12px; padding: 8px; background-color: #f8f9fa; border-radius: 6px;">
          <strong style="color: #2c3e50;">积分变化:</strong> <span style="color: ${creditChangeInfo.color}; font-weight: bold;">${creditChangeInfo.text}</span>
          ${creditChangeInfo.showTime ? `<div style="font-size: 12px; color: #95a5a6; margin-top: 4px;">自上次检查: ${lastUpdateTimeStr}</div>` : ''}
      </div>
    `

    content.innerHTML = `
      <div style="margin-bottom: 12px; padding: 8px; background-color: #f8f9fa; border-radius: 6px;">
          <strong style="color: #2c3e50;">当前余额:</strong> <span style="color: #27ae60; font-weight: bold;">${data.current_balance}</span>
      </div>
      ${creditChangeHtml}
      <div style="margin-bottom: 12px; padding: 8px; background-color: #f8f9fa; border-radius: 6px;">
          <strong style="color: #2c3e50;">套餐:</strong> <span style="text-transform: capitalize;">${data.plan}</span>
      </div>
      <div style="margin-bottom: 12px; padding: 8px; background-color: #f8f9fa; border-radius: 6px;">
          <strong style="color: #2c3e50;">模型使用情况:</strong>
          <div style="padding-left: 10px; margin-top: 5px;">${modelUsageHtml}</div>
      </div>
      <div style="padding: 8px; background-color: #f8f9fa; border-radius: 6px;">
          <strong style="color: #2c3e50;">查询时间段:</strong>
          <div style="padding-left: 10px; margin-top: 5px;">
              <div><span style="color: #7f8c8d;">开始:</span> ${startTime}</div>
              <div><span style="color: #7f8c8d;">结束:</span> ${endTime}</div>
          </div>
      </div>
    `
  }

  // 页面加载时初始化会话ID
  GM_setValue("currentSessionID", Date.now().toString())

  // 初始获取
  fetchAccountInfo()

  // 点击刷新按钮时刷新
  refreshButton.addEventListener("click", (e) => {
    e.stopPropagation()
    fetchAccountInfo()
  })

  // 每10分钟刷新一次数据
  setInterval(fetchAccountInfo, 10 * 60 * 1000)
})()
3 个赞