// ==UserScript== // @name 独角鲸云 VPS 价格排序助手 // @namespace https://dash.fuckip.me/ // @version 1.0 // @description 在独角鲸云新建实例页面展示所有地区VPS方案,按价格从低到高排序,显示库存状态 // @author You // @match https://dash.fuckip.me/deploy* // @match https://dash.fuckip.me/* // @icon https://dash.fuckip.me/favicon.ico // @grant none // @run-at document-idle // ==/UserScript== (function () { 'use strict'; // ==================== 配置 ==================== const API_BASE = 'https://api.fuckip.me/api/v1'; const REFRESH_INTERVAL = 5 * 60 * 1000; // 5分钟自动刷新 // ==================== 样式注入 ==================== const STYLES = ` #narwhal-sort-panel { position: fixed; top: 70px; right: 16px; width: 480px; max-height: 85vh; background: #1a1b2e; border: 1px solid #2d2f45; border-radius: 12px; box-shadow: 0 8px 32px rgba(0,0,0,0.4); z-index: 10000; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; overflow: hidden; transition: all 0.3s ease; } #narwhal-sort-panel.collapsed { width: 44px; height: 44px; max-height: 44px; border-radius: 22px; cursor: pointer; display: flex; align-items: center; justify-content: center; } #narwhal-sort-panel.collapsed .panel-content { display: none; } #narwhal-sort-panel.collapsed .panel-toggle { display: none; } #narwhal-sort-panel.collapsed .panel-collapsed-icon { display: block; } .panel-header { display: flex; align-items: center; justify-content: space-between; padding: 12px 16px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; font-size: 14px; font-weight: 600; cursor: move; user-select: none; } .panel-header .header-left { display: flex; align-items: center; gap: 8px; } .panel-toggle { background: rgba(255,255,255,0.2); border: none; color: white; padding: 2px 8px; border-radius: 4px; cursor: pointer; font-size: 12px; } .panel-toggle:hover { background: rgba(255,255,255,0.3); } .panel-toolbar { display: flex; align-items: center; gap: 8px; padding: 8px 16px; background: #1e1f36; border-bottom: 1px solid #2d2f45; flex-wrap: wrap; } .panel-toolbar select, .panel-toolbar input { background: #2d2f45; border: 1px solid #3d3f55; color: #e0e0e0; padding: 4px 8px; border-radius: 6px; font-size: 12px; outline: none; } .panel-toolbar select:focus, .panel-toolbar input:focus { border-color: #667eea; } .panel-toolbar input::placeholder { color: #666; } .toolbar-btn { background: #667eea; border: none; color: white; padding: 4px 10px; border-radius: 6px; cursor: pointer; font-size: 12px; white-space: nowrap; } .toolbar-btn:hover { background: #7c8ff0; } .toolbar-btn.danger { background: #e74c3c; } .toolbar-btn.danger:hover { background: #ff5e4f; } .stats-text { font-size: 11px; color: #888; margin-left: auto; } .panel-content { overflow-y: auto; max-height: calc(85vh - 120px); padding: 8px; } .panel-content::-webkit-scrollbar { width: 6px; } .panel-content::-webkit-scrollbar-track { background: transparent; } .panel-content::-webkit-scrollbar-thumb { background: #3d3f55; border-radius: 3px; } .vps-card { background: #252640; border: 1px solid #2d2f45; border-radius: 8px; padding: 10px 12px; margin-bottom: 6px; transition: all 0.2s ease; cursor: default; } .vps-card:hover { border-color: #667eea; background: #2a2b48; transform: translateX(-2px); } .vps-card.sold-out { opacity: 0.55; } .vps-card.is-free { border-color: #2ecc71; background: rgba(46,204,113,0.05); } .vps-card.is-free:hover { background: rgba(46,204,113,0.1); } .card-top { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 6px; } .card-name { font-size: 13px; font-weight: 600; color: #e0e0e0; max-width: 280px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .card-region { display: inline-flex; align-items: center; gap: 4px; font-size: 11px; color: #aaa; background: #1a1b2e; padding: 2px 6px; border-radius: 4px; } .card-specs { display: flex; gap: 10px; flex-wrap: wrap; font-size: 11px; color: #888; margin-bottom: 6px; } .card-specs span { display: flex; align-items: center; gap: 3px; } .spec-label { color: #666; } .card-bottom { display: flex; justify-content: space-between; align-items: center; } .card-price { font-size: 16px; font-weight: 700; color: #2ecc71; } .card-price.is-free { color: #2ecc71; } .card-price.is-expensive { color: #e74c3c; } .card-tags { display: flex; gap: 4px; flex-wrap: wrap; } .tag { font-size: 10px; padding: 1px 5px; border-radius: 3px; background: #3d3f55; color: #aaa; } .tag.hot { background: rgba(231,76,60,0.2); color: #e74c3c; } .tag.cool { background: rgba(46,204,113,0.2); color: #2ecc71; } .stock-badge { font-size: 11px; padding: 2px 8px; border-radius: 10px; font-weight: 500; } .stock-badge.in-stock { background: rgba(46,204,113,0.15); color: #2ecc71; } .stock-badge.limited { background: rgba(241,196,15,0.15); color: #f1c40f; } .stock-badge.out-of-stock { background: rgba(231,76,60,0.15); color: #e74c3c; } .panel-footer { padding: 8px 16px; background: #1e1f36; border-top: 1px solid #2d2f45; display: flex; justify-content: space-between; align-items: center; font-size: 11px; color: #666; } .loading-overlay { display: flex; align-items: center; justify-content: center; padding: 40px; color: #888; font-size: 13px; gap: 8px; } .loading-spinner { width: 16px; height: 16px; border: 2px solid #3d3f55; border-top: 2px solid #667eea; border-radius: 50%; animation: spin 0.8s linear infinite; } @keyframes spin { to { transform: rotate(360deg); } } .error-msg { padding: 20px; text-align: center; color: #e74c3c; font-size: 13px; } .panel-collapsed-icon { display: none; font-size: 20px; } `; // 注入样式 const styleEl = document.createElement('style'); styleEl.textContent = STYLES; document.head.appendChild(styleEl); // ==================== 工具函数 ==================== function getToken() { try { const authStr = localStorage.getItem('auth-storage'); if (!authStr) return null; const auth = JSON.parse(authStr); return auth?.state?.token || null; } catch (e) { return null; } } function formatPrice(price) { if (price === 0) return 'FREE'; return '$' + price.toFixed(2) + '/mo'; } function getPriceClass(price) { if (price === 0) return 'is-free'; if (price >= 0.5) return 'is-expensive'; return ''; } function getStockBadge(soldOut, available, maxQty) { if (soldOut || available === 0) { return '售罄'; } if (available <= 5) { return `仅剩 ${available}`; } return `库存 ${available}`; } function formatRam(mb) { if (mb >= 1024) return (mb / 1024) + 'GB'; return mb + 'MB'; } function formatTraffic(gb) { if (gb >= 1000) return (gb / 1000).toFixed(gb % 1000 === 0 ? 0 : 1) + 'TB'; return gb + 'GB'; } function getTagClass(tag) { const hotWords = ['解锁', '快乐', '直连', 'CN2', 'cn2']; const coolWords = ['落地', '独立']; if (hotWords.some(w => tag.includes(w))) return 'hot'; if (coolWords.some(w => tag.includes(w))) return 'cool'; return ''; } // ==================== 主逻辑 ==================== let allPlans = []; let currentSort = 'price-asc'; let currentRegion = 'all'; let searchKeyword = ''; let hideSoldOut = false; // 创建面板 DOM function createPanel() { const panel = document.createElement('div'); panel.id = 'narwhal-sort-panel'; panel.innerHTML = `
🐋
🐋 独角鲸 VPS 排序
加载中...
`; document.body.appendChild(panel); return panel; } // 加载数据 async function loadPlans() { const token = getToken(); const listEl = document.getElementById('ns-list'); if (!token) { listEl.innerHTML = '
❌ 未检测到登录信息,请先登录独角鲸云
'; return; } listEl.innerHTML = '
加载中...
'; try { // 获取所有分页数据 let allData = []; let page = 1; while (true) { const resp = await fetch(`${API_BASE}/plans?page=${page}&page_size=100`, { headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' } }); if (!resp.ok) throw new Error(`HTTP ${resp.status}`); const data = await resp.json(); const plans = data?.data?.plans || []; allData = allData.concat(plans); const total = data?.data?.total || 0; if (allData.length >= total || plans.length === 0) break; page++; } allPlans = allData; updateRegionFilter(); renderPlans(); // 更新时间 const now = new Date(); document.getElementById('ns-last-update').textContent = `更新: ${now.getHours().toString().padStart(2,'0')}:${now.getMinutes().toString().padStart(2,'0')}:${now.getSeconds().toString().padStart(2,'0')}`; } catch (e) { listEl.innerHTML = `
❌ 加载失败: ${e.message}
请检查登录状态或稍后重试
`; } } // 更新地区筛选器 function updateRegionFilter() { const regionSelect = document.getElementById('ns-region'); const regions = [...new Set(allPlans.map(p => p.machine_region))].sort(); regionSelect.innerHTML = ''; regions.forEach(r => { const count = allPlans.filter(p => p.machine_region === r).length; regionSelect.innerHTML += ``; }); } // 筛选 & 排序 function getFilteredPlans() { let filtered = [...allPlans]; // 地区筛选 if (currentRegion !== 'all') { filtered = filtered.filter(p => p.machine_region === currentRegion); } // 关键词搜索 if (searchKeyword) { const kw = searchKeyword.toLowerCase(); filtered = filtered.filter(p => (p.machine_name || '').toLowerCase().includes(kw) || (p.name || '').toLowerCase().includes(kw) || (p.machine_description || '').toLowerCase().includes(kw) || (p.machine_region || '').toLowerCase().includes(kw) || (p.machine_tags || []).some(t => t.toLowerCase().includes(kw)) ); } // 隐藏售罄 if (hideSoldOut) { filtered = filtered.filter(p => !p.sold_out); } // 排序 switch (currentSort) { case 'price-asc': filtered.sort((a, b) => a.price_monthly - b.price_monthly); break; case 'price-desc': filtered.sort((a, b) => b.price_monthly - a.price_monthly); break; case 'ram-desc': filtered.sort((a, b) => b.ram_mb - a.ram_mb || a.price_monthly - b.price_monthly); break; case 'traffic-desc': filtered.sort((a, b) => b.monthly_traffic_gb - a.monthly_traffic_gb || a.price_monthly - b.price_monthly); break; case 'region': filtered.sort((a, b) => a.machine_region.localeCompare(b.machine_region) || a.price_monthly - b.price_monthly); break; } return filtered; } // 渲染列表 function renderPlans() { const listEl = document.getElementById('ns-list'); const plans = getFilteredPlans(); // 更新统计 const total = plans.length; const inStock = plans.filter(p => !p.sold_out).length; document.getElementById('ns-stats').textContent = `${total}个方案 / ${inStock}个有货`; if (plans.length === 0) { listEl.innerHTML = '
没有匹配的方案
'; return; } let lastRegion = ''; let html = ''; plans.forEach(p => { // 地区分组标题 if (currentSort === 'region' && p.machine_region !== lastRegion) { lastRegion = p.machine_region; html += `
📍 ${lastRegion}
`; } const available = p.sold_out ? 0 : (p.max_quantity - p.sold_quantity); const priceClass = getPriceClass(p.price_monthly); const isFree = p.price_monthly === 0 ? 'is-free' : ''; const soldOutClass = p.sold_out ? 'sold-out' : ''; const tags = (p.machine_tags || []).map(t => `${t}` ).join(''); html += `
${p.machine_name}
${getStockBadge(p.sold_out, available, p.max_quantity)} 📍 ${p.machine_region}
套餐: ${p.name} ${p.machine_description ? '— ' + p.machine_description : ''}
CPU: ${p.cpu}C 内存: ${formatRam(p.ram_mb)} 硬盘: ${p.disk_gb}GB 带宽: ${p.bandwidth_mbps}Mbps 流量: ${formatTraffic(p.monthly_traffic_gb)} 类型: ${p.machine_vm_type?.toUpperCase()}
${formatPrice(p.price_monthly)} ${tags ? `
${tags}
` : ''}
`; }); listEl.innerHTML = html; } // ==================== 事件绑定 ==================== function bindEvents(panel) { // 排序 document.getElementById('ns-sort').addEventListener('change', (e) => { currentSort = e.target.value; renderPlans(); }); // 地区筛选 document.getElementById('ns-region').addEventListener('change', (e) => { currentRegion = e.target.value; renderPlans(); }); // 搜索 document.getElementById('ns-search').addEventListener('input', (e) => { searchKeyword = e.target.value.trim(); renderPlans(); }); // 隐藏售罄 document.getElementById('ns-hide-sold').addEventListener('change', (e) => { hideSoldOut = e.target.checked; renderPlans(); }); // 刷新 document.getElementById('ns-refresh').addEventListener('click', () => loadPlans()); // 折叠/展开 document.getElementById('ns-collapse').addEventListener('click', () => { panel.classList.add('collapsed'); }); panel.addEventListener('click', (e) => { if (panel.classList.contains('collapsed')) { panel.classList.remove('collapsed'); } }); // 拖拽移动面板 let isDragging = false, dragX, dragY, panelX, panelY; const header = panel.querySelector('.panel-header'); header.addEventListener('mousedown', (e) => { if (e.target.closest('button')) return; isDragging = true; dragX = e.clientX; dragY = e.clientY; const rect = panel.getBoundingClientRect(); panelX = rect.left; panelY = rect.top; panel.style.right = 'auto'; panel.style.left = panelX + 'px'; panel.style.top = panelY + 'px'; e.preventDefault(); }); document.addEventListener('mousemove', (e) => { if (!isDragging) return; panel.style.left = (panelX + e.clientX - dragX) + 'px'; panel.style.top = (panelY + e.clientY - dragY) + 'px'; }); document.addEventListener('mouseup', () => { isDragging = false; }); } // ==================== 启动 ==================== function init() { // 等待页面加载完成 const timer = setInterval(() => { const token = getToken(); if (token) { clearInterval(timer); const panel = createPanel(); bindEvents(panel); loadPlans(); // 定时刷新 setInterval(() => loadPlans(), REFRESH_INTERVAL); } }, 1000); // 超时保护 setTimeout(() => { clearInterval(timer); if (!document.getElementById('narwhal-sort-panel')) { const panel = createPanel(); bindEvents(panel); loadPlans(); } }, 15000); } // 启动 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })();