您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
查詢是否買幣
// ==UserScript== // @name MSU 掛幣檢測器 // @namespace http://tampermonkey.net/ // @version 0.2 // @author Alex from MyGOTW // @description 查詢是否買幣 // @match https://msu.io/* // @grant none // @run-at document-end // @license MIT // ==/UserScript== (function() { 'use strict'; const STORAGE_KEY = 'msu_activity_data'; const MAX_PAGES = 10; function weiToEth(wei) { const eth = Number(wei) / Math.pow(10, 18); if (!eth || eth < 0.0001) { return NaN; } return parseFloat(eth.toFixed(4)); } async function initialize() { console.log('Initialize being called with URL:', window.location.href); const matchUrl = /\?tab=activity(?:&page=\d+)?$/; if (!window.location.href.includes('/marketplace/inventory/')) { return; } else if(matchUrl.test(window.location.search) || window.location.search.includes('activityType=ActivityType_Buy') || window.location.search.includes('activityType=ActivityType_Sell')){ try { console.log('啟動掛幣檢測器'); // 先顯示讀取中 UI createLoadingUI(); const data = await getHistoryData(); if (data && data.length > 0) { const suspiciousActivities = filterSuspiciousActivities(data); console.log('可疑交易清單:', suspiciousActivities); console.log(`找到 ${suspiciousActivities.length} 筆可疑交易`); // 移除讀取中 UI,顯示結果 const loadingElement = document.getElementById('msu-notification'); if (loadingElement) { loadingElement.remove(); } createNotificationUI(suspiciousActivities); } } catch (error) { console.error('getHistoryData error:', error); // 發生錯誤時也要移除讀取中 UI const loadingElement = document.getElementById('msu-notification'); if (loadingElement) { loadingElement.remove(); } // 顯示錯誤通知 createErrorUI(); } } } const getHistoryData = async () => { try { const pathName = window.location.pathname; const walletAddress = pathName.split('/')[3]; console.log('walletAddress:', walletAddress) if(walletAddress){ // 檢查 localStorage 中的錢包地址是否相同 const storedData = getStoredData(); if(storedData?.walletAddress !== walletAddress) { clearStoredData(); } if(storedData?.data && storedData.walletAddress === walletAddress) { console.log('使用已儲存的資料:', storedData.data); // 即使使用快取資料,也稍微延遲一下以顯示讀取動畫 await new Promise(resolve => setTimeout(resolve, 500)); return storedData.data; } let fullData = []; const firstData = await fetchData(walletAddress, 1); if(!firstData) return; fullData.push(firstData); const totalPages = Math.min( Math.ceil(firstData.paginationResult.totalCount / firstData.paginationResult.pageSize), MAX_PAGES ); for(let i = 2; i <= totalPages; i++){ const data = await fetchData(walletAddress, i); if(data) { fullData.push(data); } await new Promise(resolve => setTimeout(resolve, 50)); } saveData(walletAddress, fullData); console.log('完整資料:', fullData); return fullData; } } catch (error) { console.error('getHistoryData error:', error); throw error; // 向上拋出錯誤以觸發錯誤處理 } } // localStorage 相關函數 function getStoredData() { try { const data = localStorage.getItem(STORAGE_KEY); return data ? JSON.parse(data) : null; } catch (error) { console.error('讀取儲存資料錯誤:', error); return null; } } function saveData(walletAddress, data) { try { const saveObj = { walletAddress, data, timestamp: Date.now() }; localStorage.setItem(STORAGE_KEY, JSON.stringify(saveObj)); } catch (error) { console.error('儲存資料錯誤:', error); } } function clearStoredData() { localStorage.removeItem(STORAGE_KEY); } const fetchData = async (walletAddress, pageNo = 1) => { try { const response = await fetch(`https://msu.io/marketplace/api/marketplace/inventory/${walletAddress}/activities?activityType=ActivityType_All&tokenType=items&paginationParam.pageNo=${pageNo}&paginationParam.pageSize=30`, { "headers": { "accept": "*/*", "accept-language": "zh-TW,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6", "content-type": "application/json", "priority": "u=1, i" }, "referrer": `https://msu.io/marketplace/inventory/${walletAddress}?tab=activity`, "referrerPolicy": "origin-when-cross-origin", "body": null, "method": "GET", "mode": "cors", "credentials": "include" }); const data = await response.json(); return data; } catch (error) { console.error('fetchData error:', error); return null; } } function filterSuspiciousActivities(fullData) { try { // 整合所有頁面的 activities,並保留頁數資訊 const allActivities = fullData.reduce((acc, page) => { const activitiesWithPage = page.activities.map(activity => ({ ...activity, pageInfo: { pageNo: page.paginationResult.currPageNo, totalCount: page.paginationResult.totalCount, pageSize: page.paginationResult.pageSize, isLastPage: page.paginationResult.isLastPage } })); return acc.concat(activitiesWithPage); }, []); // 篩選可疑交易 const suspiciousActivities = allActivities.filter(activity => { const ethPrice = weiToEth(activity.priceWei); return activity.activityType === "ActivityType_Sell" && ethPrice >= 100; }); // 保留完整資料並格式化 const formattedActivities = suspiciousActivities.map(activity => ({ // 物品資訊 tokenId: activity.tokenId, name: activity.name, imageUrl: activity.imageUrl, mintingNo: activity.mintingNo, potentialGrade: activity.potentialGrade, // 交易資訊 price: { wei: activity.priceWei, eth: weiToEth(activity.priceWei) }, quantity: activity.quantity, activityType: activity.activityType, // 賣家資訊 seller: { address: activity.walletAddrFrom, nickname: activity.nicknameFrom, profileUrl: `https://msu.io/marketplace/inventory/${activity.walletAddrFrom}` }, // 買家資訊 buyer: { address: activity.walletAddrTo, nickname: activity.nicknameTo, profileUrl: `https://msu.io/marketplace/inventory/${activity.walletAddrTo}` }, // 時間資訊 time: { original: activity.createdAt, formatted: new Date(activity.createdAt).toLocaleString('zh-TW') }, // 物品連結 itemUrl: `https://msu.io/marketplace/nft/${activity.tokenId}`, // 分頁資訊 pageInfo: activity.pageInfo, activityUrl: `https://msu.io/marketplace/inventory/${activity.walletAddrFrom}?tab=activity&page=${activity.pageInfo.pageNo}` })); console.log('可疑交易清單:', formattedActivities); console.log(`找到 ${formattedActivities.length} 筆可疑交易`); // 建立通知 UI createNotificationUI(formattedActivities); return formattedActivities; } catch (error) { console.error('篩選可疑交易時發生錯誤:', error); return []; } } function createNotificationUI(suspiciousActivities) { // 檢查是否已存在通知 UI if (document.getElementById('msu-notification')) { return; } // 創建樣式 const style = document.createElement('style'); style.textContent = ` #msu-notification { position: fixed; left: 20px; bottom: 20px; z-index: 9999; font-family: Arial, sans-serif; } .notification-btn { padding: 10px 20px; border-radius: 5px; border: none; cursor: pointer; font-weight: bold; transition: all 0.3s ease; } .normal-btn { background-color: #4CAF50; color: white; } .warning-btn { background-color: #ff4444; color: white; animation: pulse 2s infinite; } @keyframes pulse { 0% { transform: scale(1); } 50% { transform: scale(1.05); } 100% { transform: scale(1); } } .detail-window { display: none; position: fixed; left: 50%; top: 50%; transform: translate(-50%, -50%); background: #1b1b1b; color: white; padding: 20px; border-radius: 10px; box-shadow: 0 0 20px rgba(0,0,0,0.5); max-width: 80vw; max-height: 80vh; overflow-y: auto; z-index: 10000; } .detail-window h2 { color: white; margin-bottom: 20px; } .detail-window.show { display: block; } .close-btn { position: absolute; right: 10px; top: 10px; cursor: pointer; font-size: 20px; color: #ffffff; } .suspicious-table { width: 100%; border-collapse: collapse; margin-top: 10px; color: white; } .suspicious-table th, .suspicious-table td { padding: 10px; border: 1px solid #333; text-align: left; } .suspicious-table th { background-color: #2d2d2d; color: white; } .suspicious-table tr { background-color: #1b1b1b; } .suspicious-table tr:hover { background-color: #2d2d2d; } .item-image { width: 50px; height: 50px; object-fit: contain; border-radius: 5px; } .address-link { color: #66b3ff; text-decoration: none; } .address-link:hover { text-decoration: underline; color: #99ccff; } .overlay { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.7); z-index: 9999; } .overlay.show { display: block; } /* 捲軸樣式 */ .detail-window::-webkit-scrollbar { width: 8px; } .detail-window::-webkit-scrollbar-track { background: #2d2d2d; border-radius: 4px; } .detail-window::-webkit-scrollbar-thumb { background: #4d4d4d; border-radius: 4px; } .detail-window::-webkit-scrollbar-thumb:hover { background: #666; } /* 一般連結樣式 */ .detail-window a { color: #66b3ff; text-decoration: none; } .detail-window a:hover { color: #99ccff; text-decoration: underline; } `; document.head.appendChild(style); // 創建通知按鈕和詳細資訊視窗 const notificationDiv = document.createElement('div'); notificationDiv.id = 'msu-notification'; const buttonText = suspiciousActivities.length > 0 ? `疑似買幣 (${suspiciousActivities.length})` : '帳戶正常'; const buttonClass = suspiciousActivities.length > 0 ? 'warning-btn' : 'normal-btn'; notificationDiv.innerHTML = ` <button class="notification-btn ${buttonClass}">${buttonText}</button> <div class="overlay"></div> <div class="detail-window"> <span class="close-btn">×</span> <h2>可疑交易清單</h2> <table class="suspicious-table"> <thead> <tr> <th>物品</th> <th>名稱</th> <th>價格</th> <th>賣家</th> <th>買家</th> <th>時間</th> <th>頁數</th> </tr> </thead> <tbody> ${suspiciousActivities.map(activity => ` <tr> <td> <a href="${activity.itemUrl}" target="_blank"> <img src="${activity.imageUrl}" class="item-image" alt="${activity.name}"> </a> </td> <td> <a href="${activity.itemUrl}" target="_blank"> ${activity.name} #${activity.mintingNo} </a> </td> <td>${activity.price.eth} ETH</td> <td> <a href="${activity.seller.profileUrl}" target="_blank" class="address-link"> ${activity.seller.nickname || '未知'}<br> ${activity.seller.address.slice(0, 6)}...${activity.seller.address.slice(-4)} </a> </td> <td> <a href="${activity.buyer.profileUrl}" target="_blank" class="address-link"> ${activity.buyer.nickname || '未知'}<br> ${activity.buyer.address.slice(0, 6)}...${activity.buyer.address.slice(-4)} </a> </td> <td>${activity.time.formatted}</td> <td> <a href="${activity.activityUrl}" target="_blank"> 第 ${activity.pageInfo.pageNo} 頁 </a> </td> </tr> `).join('')} </tbody> </table> </div> `; document.body.appendChild(notificationDiv); // 事件處理 const button = notificationDiv.querySelector('.notification-btn'); const detailWindow = notificationDiv.querySelector('.detail-window'); const overlay = notificationDiv.querySelector('.overlay'); const closeBtn = notificationDiv.querySelector('.close-btn'); button.addEventListener('click', () => { if (suspiciousActivities.length > 0) { detailWindow.classList.add('show'); overlay.classList.add('show'); } }); closeBtn.addEventListener('click', () => { detailWindow.classList.remove('show'); overlay.classList.remove('show'); }); overlay.addEventListener('click', () => { detailWindow.classList.remove('show'); overlay.classList.remove('show'); }); } // 先建立一個簡單的讀取中 UI function createLoadingUI() { // 檢查是否已存在 if (document.getElementById('msu-notification')) { return; } const style = document.createElement('style'); style.textContent = ` .loading-btn { background-color: #666 !important; position: relative; cursor: wait !important; } .loading-btn::after { content: ''; position: absolute; width: 16px; height: 16px; top: 50%; right: 10px; transform: translateY(-50%); border: 2px solid #ffffff; border-radius: 50%; border-right-color: transparent; animation: rotate 1s linear infinite; } @keyframes rotate { from { transform: translateY(-50%) rotate(0deg); } to { transform: translateY(-50%) rotate(360deg); } } `; document.head.appendChild(style); const notificationDiv = document.createElement('div'); notificationDiv.id = 'msu-notification'; notificationDiv.innerHTML = ` <button class="notification-btn loading-btn">資料讀取中...</button> `; document.body.appendChild(notificationDiv); } // 新增錯誤通知 UI function createErrorUI() { const notificationDiv = document.createElement('div'); notificationDiv.id = 'msu-notification'; notificationDiv.innerHTML = ` <button class="notification-btn" style="background-color: #ff4444;"> 資料載入失敗 </button> `; document.body.appendChild(notificationDiv); // 3秒後自動移除錯誤通知 setTimeout(() => { const errorElement = document.getElementById('msu-notification'); if (errorElement) { errorElement.remove(); } }, 3000); } initialize(); // URL 變化監聽 const originalPushState = history.pushState; const originalReplaceState = history.replaceState; history.pushState = function (...args) { originalPushState.apply(this, args); handleUrlChange('pushState'); }; history.replaceState = function (...args) { originalReplaceState.apply(this, args); handleUrlChange('replaceState'); }; window.addEventListener('popstate', function () { handleUrlChange('popstate'); }); function handleUrlChange(method) { console.log(`小精靈通知: [${method}] URL 已變化: ${window.location.href}`); initialize(); } })();