积分来源与抽奖机制同步,增加快捷签到的等级进度插件(v1.6.0)

经过这两天的测试结果,1.6.0来了!


一、优化说明:

  1. 因为存在积分明细和积分排行不一致的会员,同时经过与dabo大佬关于抽奖系统的确认,修改了积分的显示来源,使插件显示积分满足抽奖机制的判断机制。
  2. 新增快捷签到,可以不需要到签到页面完成签到,同时显示当日签到积分、连续签到天数和历史签到天数。

二、功能

1.未签到状态:

2.签到完成状态:

3.签到完成后签到页面:

4.签到完成后个人总结页面:

Tips:因为论坛的底层对积分不是实时更新,所以个人总结页面的积分实际在刚签到完后是少于签到页面的。静待积分同步即可。

三、插件代码:

// ==UserScript==
// @name         MJJBOX 等级进度查看器(积分优化+签到版)
// @namespace    http://tampermonkey.net/
// @version      1.6.0
// @description  依据官方默认等级规则显示进度,积分获取来源于抽奖机制同步,增加快捷签到。
// @author       Aimei
// @match        https://mjjbox.com/*
// @grant        GM_xmlhttpRequest
// @grant        GM_getValue
// @grant        GM_setValue
// ==/UserScript==

(() => {
  'use strict';
  if (window !== window.top) return;

  /* ========== 等级名称(与官方同步) ========== */
  const levelNames = {
    0: '青铜会员',
    1: '白银会员',
    2: '黄金会员',
    3: '钻石会员',
    4: '星曜会员'
  };

  /* ========== 官方默认晋级条件(完全同步) ========== */
  const levelRequirements = {
    1: {
      topics_entered: 20,
      posts_read: 30,
      time_read: 60 * 60
    },
    2: {
      days_visited: 15,
      topics_entered: 20,
      posts_read: 100,
      time_read: 60 * 60,
      posts_created: 1,
      likes_received: 15,
      likes_given: 20,
      has_avatar: true,
      has_bio: true
    },
    3: {
      days_visited_in_100: 50,
      topics_entered: 500,
      posts_read: 9000,
      posts_created_in_100: 10,
      likes_received: 50,
      likes_given: 100,
      flagged_posts_ratio: 0.05
    },
    4: {
      manual_promotion: true
    }
  };

  /* ========== CSS(黑字 + 绿/红状态色) ========== */
  const styles = `
    .mjjbox-level-badge {
      position: fixed; top: 20px; right: 20px;
      width: 56px; height: 56px; border-radius: 50%;
      display: flex; align-items: center; justify-content: center;
      font-weight: 700; font-size: 12px; color: #fff;
      cursor: pointer; z-index: 9999;
      box-shadow: 0 4px 20px rgba(0,0,0,.25);
      transition: transform .3s,box-shadow .3s;
      border: 2px solid #fff;
    }
    .mjjbox-level-badge:hover { transform: scale(1.1); box-shadow: 0 6px 25px rgba(0,0,0,.3); }
    .mjjbox-level-badge.level-0 { background: linear-gradient(135deg,#9ca3af 0%,#6b7280 100%); }
    .mjjbox-level-badge.level-1 { background: linear-gradient(135deg,#10b981 0%,#059669 100%); }
    .mjjbox-level-badge.level-2 { background: linear-gradient(135deg,#3b82f6 0%,#1d4ed8 100%); }
    .mjjbox-level-badge.level-3 { background: linear-gradient(135deg,#8b5cf6 0%,#7c3aed 100%); }
    .mjjbox-level-badge.level-4 { background: linear-gradient(135deg,#f59e0b 0%,#d97706 100%); }

    .mjjbox-level-modal {
      position: fixed; inset: 0;
      background: rgba(0,0,0,.42); z-index: 10000;
      opacity: 0; visibility: hidden;
      transition: opacity .35s,visibility .35s;
      backdrop-filter: blur(6px);
    }
    .mjjbox-level-modal.show { opacity: 1; visibility: visible; }

    .mjjbox-level-modal-content {
      position: absolute;
      background: #fff; border-radius: 12px;
      width: 400px; max-width: 90vw; max-height: 80vh;
      padding: 16px 16px 24px;
      box-shadow: 0 16px 40px rgba(0,0,0,.3);
      overflow-y: auto;
      transform: scale(.9) translateY(-20px);
      transition: transform .35s;
      /* 滚动条样式 (Firefox) */
      scrollbar-width: thin;
      scrollbar-color: #c1c1c1 #f3f3f3;
    }

    /* WebKit 滚动条样式 (Chrome, Edge, Safari) */
    .mjjbox-level-modal-content::-webkit-scrollbar {
      width: 6px;
    }
    .mjjbox-level-modal-content::-webkit-scrollbar-track {
      background: #f3f3f3;
      border-radius: 3px;
    }
    .mjjbox-level-modal-content::-webkit-scrollbar-thumb {
      background: #c1c1c1;
      border-radius: 3px;
    }
    .mjjbox-level-modal-content::-webkit-scrollbar-thumb:hover {
      background: #a8a8a8;
    }

    /* 小屏适配:在更小的屏幕上进一步缩小 */
    @media (max-width: 480px) {
      .mjjbox-level-modal-content {
        width: 320px;
        max-width: 92vw;
        padding: 14px 14px 22px;
        border-radius: 10px;
      }
      .mjjbox-level-title { font-size: 20px; }
      .mjjbox-level-subtitle, .mjjbox-level-score { font-size: 14px; }
      .mjjbox-progress-label { font-size: 14px; }
      .mjjbox-progress-required, .mjjbox-progress-tooltip { font-size: 12px; }
      .mjjbox-close-btn { font-size: 24px; top: 8px; right: 12px; }
    }

    .mjjbox-close-btn {
      position: absolute; top: 10px; right: 14px;
      background: none; border: none;
      font-size: 26px; cursor: pointer; color: #000;
    }

    .mjjbox-level-header { text-align: center; }
    .mjjbox-level-title { margin: 0; font-size: 20px; font-weight: 700; color: #000; }
    .mjjbox-level-subtitle, .mjjbox-level-score { margin: 4px 0 0; font-size: 14px; color: #000; }

    .mjjbox-progress-section h3 { margin: 0 0: 14px; font-size: 16px; color: #000; }
    .mjjbox-progress-item { margin-bottom: 10px; }
    .mjjbox-progress-label { display: block; font-weight: 600; margin-bottom: 4px; color: #000; font-size: 14px; }
    .mjjbox-progress-bar-container { display: flex; align-items: center; gap: 6px; }
    .mjjbox-progress-bar { flex: 1; height: 6px; background: #f3f4f6; border-radius: 3px; overflow: hidden; }
    .mjjbox-progress-fill { height: 100%; background: linear-gradient(90deg,#10b981 0%,#34d399 100%); transition: width .4s; }
    .mjjbox-progress-fill.incomplete { background: linear-gradient(90deg,#f87171 0%,#fca5a5 100%); }
    .mjjbox-progress-required, .mjjbox-progress-tooltip { font-size: 12px; color: #000; }
    .mjjbox-progress-undone { color: #ef4444; }
    .mjjbox-progress-done   { color: #10b981; }

    /* 签到区域样式 */
    .mjjbox-checkin-section {
      margin: 20px 0 10px;
      padding: 15px;
      background: #f8fafc;
      border-radius: 8px;
      text-align: center;
    }
    .mjjbox-checkin-status {
      font-size: 14px;
      color: #000;
      margin-bottom: 12px;
    }
    .mjjbox-checkin-button {
      background: linear-gradient(135deg,#3b82f6 0%,#1d4ed8 100%);
      color: white;
      border: none;
      padding: 8px 16px;
      border-radius: 6px;
      cursor: pointer;
      font-weight: 600;
      transition: opacity 0.2s;
    }
    .mjjbox-checkin-button:hover {
      opacity: 0.9;
    }
    .mjjbox-checkin-button:disabled {
      background: #9ca3af;
      cursor: not-allowed;
    }
  `;
  const styleSheet = document.createElement('style');
  styleSheet.textContent = styles;
  document.head.appendChild(styleSheet);

  /* ========== 通用小工具 ========== */
  const getCurrentUsername = () => {
    try {
      if (typeof Discourse !== 'undefined' && Discourse.User && Discourse.User.current()) {
        return Discourse.User.current()?.username || null;
      }
    } catch (e) {
      console.error('获取用户名失败:', e);
    }
    return null;
  };

  const showNotification = (msg, type = 'info', dur = 3000) => {
    const n = document.createElement('div');
    n.style.cssText = `
      position: fixed; top: 90px; right: 24px;
      padding: 12px 18px; border-radius: 8px;
      color: #fff; font-weight: 600; z-index: 10001;
      opacity: 0; transform: translateX(120%);
      transition: all .3s;
      background: ${type === 'error' ? '#ef4444' : '#10b981'};
    `;
    n.textContent = msg;
    document.body.appendChild(n);
    setTimeout(() => { n.style.opacity = '1'; n.style.transform = 'translateX(0)'; }, 100);
    setTimeout(() => { n.style.opacity = '0'; n.style.transform = 'translateX(120%)'; setTimeout(() => n.remove(), 300); }, dur);
  };

  /* ========== 签到功能 ========== */
  const checkCheckinStatus = (callback) => {
    GM_xmlhttpRequest({
      method: 'GET',
      url: 'https://mjjbox.com/checkin.json',
      onload: (response) => {
        if (response.status === 200) {
          try {
            const data = JSON.parse(response.responseText);
            console.log('签到状态响应:', data);
            callback(data);
          } catch (e) {
            console.error('解析签到状态失败:', e);
            callback(null);
          }
        } else {
          console.warn('获取签到状态失败:', response.status);
          callback(null);
        }
      },
      onerror: (error) => {
        console.error('获取签到状态错误:', error);
        callback(null);
      }
    });
  };

  const performCheckin = (callback) => {
    // 获取CSRF token
    const csrfToken = document.querySelector('meta[name="csrf-token"]')?.content;

    if (!csrfToken) {
      console.error('无法获取CSRF token');
      callback(null, '无法获取安全令牌,请刷新页面');
      return;
    }

    console.log('发送签到请求,CSRF Token:', csrfToken.substring(0, 10) + '...');

    // 使用正确的请求格式
    GM_xmlhttpRequest({
      method: 'POST',
      url: 'https://mjjbox.com/checkin',
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
        'X-CSRF-Token': csrfToken,
        'X-Requested-With': 'XMLHttpRequest'
      },
      data: `authenticity_token=${encodeURIComponent(csrfToken)}`,
      onload: (response) => {
        console.log('签到响应状态:', response.status);

        if (response.status === 200) {
          try {
            const data = JSON.parse(response.responseText);
            console.log('签到响应数据:', data);

            // 根据实际响应格式处理
            if (data.success) {
              callback(data, null);
            } else {
              let errorMsg = data.errors?.[0] || data.error || '签到失败';
              if (errorMsg.includes('already checked in')) {
                errorMsg = '您今天已经签到了';
              }
              callback(null, errorMsg);
            }
          } catch (e) {
            console.error('解析JSON响应失败:', e, '响应内容:', response.responseText.substring(0, 200));
            callback(null, '签到响应格式异常');
          }
        } else if (response.status === 403) {
          callback(null, '权限不足,请重新登录');
        } else if (response.status === 422) {
          callback(null, '安全验证失败,请刷新页面');
        } else {
          callback(null, `签到失败,状态码: ${response.status}`);
        }
      },
      onerror: (error) => {
        console.error('签到请求错误:', error);
        callback(null, '网络错误,请稍后重试');
      }
    });
  };

  /* ========== 等级徽章 ========== */
  const createLevelBadge = () => {
    const badge = document.createElement('div');
    badge.className = 'mjjbox-level-badge';
    badge.textContent = 'LV ?';
    badge.title = '点击查看等级进度和签到';
    badge.addEventListener('click', fetchUserLevel);
    document.body.appendChild(badge);
    return badge;
  };

  const updateLevelBadge = (level, username) => {
    const badge = document.querySelector('.mjjbox-level-badge');
    if (!badge) return;
    badge.textContent = `LV ${level}`;
    badge.className = `mjjbox-level-badge level-${level}`;
    badge.title = `${username} - ${levelNames[level] || '未知等级'}(点击查看详情和签到)`;
  };

  /* ========== 拉取数据 ========== */
  const fetchUserLevel = () => {
    const username = getCurrentUsername();
    if (!username) return showNotification('❌ 无法获取当前用户名', 'error');

    let summaryData = null;
    let userData = null;
    let checkinData = null;
    let done = 0;
    const checkDone = () => { done++; if (done === 3) processUserData(summaryData, userData, checkinData, username); };

    GM_xmlhttpRequest({
      method: 'GET',
      url: `https://mjjbox.com/u/${username}/summary.json`,
      timeout: 15000,
      headers: { Accept: 'application/json', 'X-Requested-With': 'XMLHttpRequest' },
      onload: resp => { if (resp.status === 200) { try { summaryData = JSON.parse(resp.responseText); } catch {} } checkDone(); },
      onerror: checkDone
    });
    GM_xmlhttpRequest({
      method: 'GET',
      url: `https://mjjbox.com/u/${username}.json`,
      timeout: 15000,
      headers: { Accept: 'application/json', 'X-Requested-With': 'XMLHttpRequest' },
      onload: resp => { if (resp.status === 200) { try { userData = JSON.parse(resp.responseText); } catch {} } checkDone(); },
      onerror: checkDone
    });
    // 获取签到状态
    checkCheckinStatus((data) => {
      checkinData = data;
      checkDone();
    });
  };

  /* ========== 处理数据 ========== */
  const processUserData = (summaryData, userData, checkinData, username) => {
    if (!summaryData || !userData) return showNotification('❌ 获取用户数据失败', 'error');

    const user = userData.user || summaryData.users?.[0];
    const userSummary = summaryData.user_summary;

    if (user && typeof user.trust_level === 'number') {
      const level = user.trust_level;
      updateLevelBadge(level, username);
      showNotification(`✅ 等级信息获取成功:LV${level} ${levelNames[level]}`, 'success', 2000);
      const modal = createLevelModal({
        level,
        username,
        userData: { user, userSummary, gamification_score: user.gamification_score || 0 },
        checkinData
      });
      document.body.appendChild(modal);
      setTimeout(() => modal.classList.add('show'), 10);
    } else {
      showNotification('❌ 无法解析用户等级信息', 'error');
    }
  };

  /* ========== 计算进度(完全按官方字段) ========== */
  const calculateLevelProgress = (currentLevel, userData) => {
    if (!userData?.userSummary) return { items: [], achievedCount: 0, totalCount: 0 };
    const us = userData.userSummary;
    const u = userData.user;
    const next = currentLevel + 1;
    const req = levelRequirements[next];
    if (!req) return { items: [{ label: '升级方式', current: '联系管理员', required: '手动提升', isMet: false }], achievedCount: 0, totalCount: 1 };

    const items = [];
    let achieved = 0;
    const add = (label, current, required, isTime = false) => {
      const met = current >= required;
      items.push({ label, current, required, isMet: met, percentage: Math.min((current / required) * 100, 100), isTime });
      if (met) achieved++;
    };

    /* 新增:100 天内访问天数(官方字段目前需自行计算,这里先用 days_visited 近似) */
    const daysVisited100 = us.days_visited || 0;

    if (req.topics_entered !== undefined) add('阅读主题数', us.topics_entered || 0, req.topics_entered);
    if (req.posts_read !== undefined) add('阅读帖子数', us.posts_read_count || 0, req.posts_read);
    if (req.time_read !== undefined) add('总阅读时间(分钟)', Math.floor((us.time_read || 0) / 60), Math.floor(req.time_read / 60), true);
    if (req.days_visited !== undefined) add('累计访问天数', us.days_visited || 0, req.days_visited);
    if (req.days_visited_in_100 !== undefined) add('过去100天内访问天数', daysVisited100, req.days_visited_in_100);
    if (req.posts_created !== undefined) add('累计发帖数', us.topic_count || 0, req.posts_created);
    if (req.posts_created_in_100 !== undefined) add('过去100天内发帖/回复数', (us.topic_count || 0) + (us.post_count || 0), req.posts_created_in_100);
    if (req.likes_received !== undefined) add('收到赞数', us.likes_received || 0, req.likes_received);
    if (req.likes_given !== undefined) add('送出赞数', us.likes_given || 0, req.likes_given);

    /* 头像 & 个人简介 */
    if (req.has_avatar !== undefined) {
      const has = !!(u.avatar_template && !u.avatar_template.includes('letter_avatar') && !u.avatar_template.includes('system_avatar'));
      items.push({ label: '已上传头像', current: has ? '已上传' : '未上传', required: '已上传', isMet: has, isBoolean: true });
      if (has) achieved++;
    }
    if (req.has_bio !== undefined) {
      const has = !!(u.bio_raw && u.bio_raw.trim());
      items.push({ label: '已填写基本资料', current: has ? '已填写' : '未填写', required: '已填写', isMet: has, isBoolean: true });
      if (has) achieved++;
    }

    /* 被隐藏/举报比例(官方无直接字段,这里留 0 占位) */
    if (req.flagged_posts_ratio !== undefined) {
      const flaggedRatio = 0; // 暂无 API,直接给 0
      items.push({ label: '被举报/隐藏帖子比例', current: `${(flaggedRatio * 100).toFixed(1)}%`, required: `${(req.flagged_posts_ratio * 100).toFixed(0)}% 以内`, isMet: flaggedRatio <= req.flagged_posts_ratio });
      if (flaggedRatio <= req.flagged_posts_ratio) achieved++;
    }

    items.sort((a, b) => (a.isMet ? 1 : -1));
    return { items, achievedCount: achieved, totalCount: items.length };
  };

  /* ========== 弹窗(位置固定在徽章下方) ========== */
  const createLevelModal = ({ level, username, userData, checkinData }) => {
    const modal = document.createElement('div');
    modal.className = 'mjjbox-level-modal';

    const progress = calculateLevelProgress(level, userData);
    const currentName = levelNames[level] || '未知等级';

    const content = document.createElement('div');
    content.className = 'mjjbox-level-modal-content';

    // 计算位置:徽章下方
    const badgeRect = document.querySelector('.mjjbox-level-badge').getBoundingClientRect();
    let top = badgeRect.bottom + 18;
    let right = window.innerWidth - badgeRect.right;
    if (top + 500 > window.innerHeight) top = badgeRect.top - 500 - 18;
    if (right - 420 < 0) right = 10;
    content.style.top = `${top}px`;
    content.style.right = `${right}px`;

    // 处理签到状态
    let checkinStatusText = '未知';
    let isCheckedIn = false;
    let pointsEarned = 0;
    let consecutiveDays = 0;
    let totalCheckins = 0;

    // 始终使用 userData 中的 gamification_score
    let currentPoints = userData.gamification_score || 0;

    if (checkinData) {
      isCheckedIn = checkinData.today_checked_in || false;
      pointsEarned = checkinData.checkin_history && checkinData.checkin_history[0] ? checkinData.checkin_history[0].points_earned : 0;
      consecutiveDays = checkinData.consecutive_days || 0;
      totalCheckins = checkinData.user_checkin_count || 0;

      checkinStatusText = isCheckedIn ?
        `今日已签到,获得 ${pointsEarned} 积分` :
        '今日未签到';
    }

    content.innerHTML = `
      <button class="mjjbox-close-btn">&times;</button>
      <div class="mjjbox-level-header">
        <h2 class="mjjbox-level-title">${username}</h2>
        <p class="mjjbox-level-subtitle">当前等级:LV${level} ${currentName}</p>
        <p class="mjjbox-level-score">当前积分:${currentPoints}</p>
      </div>
      <div class="mjjbox-checkin-section">
        <div class="mjjbox-checkin-status">${checkinStatusText}</div>
        <div>已连续签到 ${consecutiveDays} 天,总共签到 ${totalCheckins} 次</div>
        <button class="mjjbox-checkin-button" ${isCheckedIn ? 'disabled' : ''}>
          ${isCheckedIn ? '今日已签到' : '立即签到'}
        </button>
      </div>
      <div class="mjjbox-progress-section">
        <h3>${level >= 4 ? '已达到最高等级' : `晋级到 LV${level + 1} ${levelNames[level + 1]} 的进度(${progress.achievedCount}/${progress.totalCount})`}</h3>
        ${progress.items.map(item => {
          const cur = item.isTime ? `${item.current} 分钟` : item.current;
          const need = item.isTime ? `${item.required} 分钟` : item.required;
          const met = item.isMet;
          const icon = met ? '✅' : '❌';
          return `
            <div class="mjjbox-progress-item">
              <span class="mjjbox-progress-label">${item.label}</span>
              <div class="mjjbox-progress-bar-container">
                <div class="mjjbox-progress-bar">
                  <div class="mjjbox-progress-fill ${met ? '' : 'incomplete'}" style="width: ${item.percentage || 0}%"></div>
                </div>
                <span class="mjjbox-progress-required ${met ? '' : 'mjjbox-progress-undone'}">
                  需要:${item.isBoolean ? item.required : need} ${icon}
                </span>
              </div>
              <div class="mjjbox-progress-tooltip">
                当前:<span class="${met ? 'mjjbox-progress-done' : 'mjjbox-progress-undone'}">
                  ${item.isBoolean ? item.current : cur} ${icon}
                </span>
              </div>
            </div>`;
        }).join('')}
        ${progress.items.length === 0 ? '<div style="text-align:center;padding:20px;color:#000;">🎉 恭喜!您已达到最高等级!</div>' : ''}
      </div>
    `;

    modal.appendChild(content);

    // 添加签到按钮事件
    const checkinButton = content.querySelector('.mjjbox-checkin-button');
    if (checkinButton && !isCheckedIn) {
      checkinButton.addEventListener('click', () => {
        checkinButton.disabled = true;
        checkinButton.textContent = '签到中...';

        // 先检查登录状态
        const currentUser = getCurrentUsername();
        if (!currentUser) {
          showNotification('❌ 请先登录后再签到', 'error');
          checkinButton.disabled = false;
          checkinButton.textContent = '立即签到';
          return;
        }

        performCheckin((result, error) => {
          if (result && result.success) {
            const points = result.points_earned || 10;
            const newConsecutiveDays = result.consecutive_days || consecutiveDays + 1;
            const newTotalCheckins = totalCheckins + 1;

            // 不再使用签到返回的积分,而是重新获取用户数据
            showNotification(`✅ ${result.message || `签到成功!获得 ${points} 积分`}`, 'success');

            // 立即更新UI状态
            const statusElement = content.querySelector('.mjjbox-checkin-status');
            statusElement.textContent = `今日已签到,获得 ${points} 积分`;

            // 更新连续签到和总签到次数
            const consecutiveElement = content.querySelector('.mjjbox-checkin-section > div:nth-child(2)');
            if (consecutiveElement) {
              consecutiveElement.textContent = `已连续签到 ${newConsecutiveDays} 天,总共签到 ${newTotalCheckins} 次`;
            }

            checkinButton.textContent = '今日已签到';
            checkinButton.disabled = true;

            // 重新获取用户数据以更新积分显示
            setTimeout(() => {
              const username = getCurrentUsername();
              if (username) {
                GM_xmlhttpRequest({
                  method: 'GET',
                  url: `https://mjjbox.com/u/${username}.json`,
                  onload: (resp) => {
                    if (resp.status === 200) {
                      try {
                        const newUserData = JSON.parse(resp.responseText);
                        const newPoints = newUserData.user?.gamification_score || currentPoints;

                        // 更新积分显示为最新的 gamification_score
                        const scoreElement = content.querySelector('.mjjbox-level-score');
                        scoreElement.textContent = `当前积分:${newPoints}`;

                        showNotification('✅ 积分已更新', 'success', 2000);
                      } catch (e) {
                        console.error('解析用户数据失败:', e);
                      }
                    }
                  }
                });
              }
            }, 500);

          } else {
            let errorMsg = error || '签到失败,请稍后重试';

            if (error && (error.includes('already checked in') || error.includes('已经签到了'))) {
              errorMsg = '您今天已经签到了';
              checkinButton.textContent = '今日已签到';
              checkinButton.disabled = true;

              const statusElement = content.querySelector('.mjjbox-checkin-status');
              statusElement.textContent = `今日已签到`;
            } else if (error && error.includes('请先登录')) {
              errorMsg = '请先登录后再签到';
              checkinButton.disabled = false;
              checkinButton.textContent = '立即签到';
            } else {
              checkinButton.disabled = false;
              checkinButton.textContent = '立即签到';
            }

            showNotification(`❌ ${errorMsg}`, 'error');
          }
        });
      });
    }

    content.querySelector('.mjjbox-close-btn').addEventListener('click', () => {
      modal.classList.remove('show');
      setTimeout(() => modal.remove(), 300);
    });
    modal.addEventListener('click', e => {
      if (e.target === modal) {
        modal.classList.remove('show');
        setTimeout(() => modal.remove(), 300);
      }
    });
    document.addEventListener('keydown', function esc(e) {
      if (e.key === 'Escape') {
        modal.classList.remove('show');
        setTimeout(() => modal.remove(), 300);
        document.removeEventListener('keydown', esc);
      }
    });
    return modal;
  };

  /* ========== 启动 ========== */
  const init = () => {
    if (document.readyState === 'loading') return document.addEventListener('DOMContentLoaded', init);
    createLevelBadge();
  };
  init();
})();

四、如何安装?

1.安装油猴

2.赋予开发者权限

管理拓展页面找到开发模式,各个浏览器叫法不同:
image

  • Chrome浏览器: 开发者模式
  • Edge浏览器: 开发人员模式

3.安装脚本

  1. 复制 插件代码 文件的全部内容
  2. 点击浏览器右上角的 Tampermonkey 图标
  3. 选择"添加新脚本"
  4. 删除默认内容,粘贴复制脚本代码
  5. 选择文件下的保存按钮即可保存。
  6. 返回MJJBOX论坛,右上角将会出现一个圆形徽章,点击即可使用相关功能。

五、其他说明

由于权限等问题,所有进度插件都卡在升钻石的过去100天访问这个数据上,由于暂时拿不到这个真实数据,所以如果你发现你条件都满足,但无法升级钻石,可能就是这个数据的异常,只要正常访问,基本后面都能升级。

44 个赞

前排点赞

1 个赞

大佬牛逼

2 个赞

大佬牛逼

2 个赞

大佬牛逼

2 个赞

为大佬点赞:+1:

2 个赞

用上了 :+1:

2 个赞

叫huige 开个只读api,然后调用管理api获取升级钻石的状态,只能这样处理,或者安装插件提供api,组件显示进度

1 个赞

怕huige搞不来 后面再看吧 我是想把这玩意写到组件里

4 个赞

组件才是根本,油猴脚本解决不了,或者先像L站提供一个conect一样

3 个赞

你写个插件完事了啊

2 个赞

慢慢来 先把功能完善 改写比较方便

5 个赞

真棒

3 个赞

已经用上了,感谢大佬

3 个赞

厉害

3 个赞

找这个找了好久

2 个赞

方便

1 个赞

用上了

这个好这个好

1 个赞

怎么等到了

1 个赞