Microsoft Rewards Dashboard

自动完成Microsoft Rewards每日搜索任务,显示今日积分获取进度,自动计算搜索次数

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

You will need to install an extension such as Tampermonkey to install this script.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name         Microsoft Rewards Dashboard
// @namespace    http://tampermonkey.net/
// @version      2.0.8
// @description  自动完成Microsoft Rewards每日搜索任务,显示今日积分获取进度,自动计算搜索次数
// @author       MIANKRAM
// @icon         https://seikan.lat/api/Rewards Asset/favicon.ico
// @icon64       https://seikan.lat/api/Rewards Asset/bing.png
// @match        https://www.bing.com/*
// @match        https://cn.bing.com/*
// @match        https://rewards.bing.com/*
// @grant        GM_xmlhttpRequest
// @grant        GM_addStyle
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // 配置
    const API_URL = "https://rewards.bing.com/api/getuserinfo?type=1&X-Requested-With=XMLHttpRequest&";

    // 样式
    const styles = `
        #mr-dashboard-container {
            position: fixed;
            top: 20%;
            right: 0;
            z-index: 9999;
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            display: flex;
            align-items: flex-start;
            transition: transform 0.3s ease;
        }
        
        #mr-dashboard-toggle {
            background-color: #0078d4;
            color: white;
            padding: 10px 5px;
            cursor: pointer;
            border-top-left-radius: 5px;
            border-bottom-left-radius: 5px;
            writing-mode: vertical-rl;
            text-orientation: mixed;
            font-size: 14px;
            box-shadow: -2px 2px 5px rgba(0,0,0,0.2);
        }

        #mr-dashboard-panel {
            background-color: white;
            width: 280px;
            padding: 15px;
            box-shadow: -2px 2px 10px rgba(0,0,0,0.2);
            border-bottom-left-radius: 5px;
            display: none; /* 默认隐藏 */
            color: #333;
        }

        #mr-dashboard-container.expanded #mr-dashboard-panel {
            display: block;
        }

        .mr-section {
            margin-bottom: 15px;
            border-bottom: 1px solid #eee;
            padding-bottom: 10px;
        }

        .mr-section:last-child {
            border-bottom: none;
        }

        .mr-title {
            font-weight: bold;
            font-size: 16px;
            margin-bottom: 10px;
            color: #0078d4;
        }

        .mr-item {
            display: flex;
            justify-content: space-between;
            margin-bottom: 5px;
            font-size: 14px;
        }

        .mr-progress-bar {
            height: 6px;
            background-color: #e0e0e0;
            border-radius: 3px;
            margin-top: 2px;
            overflow: hidden;
        }

        .mr-progress-fill {
            height: 100%;
            background-color: #0078d4;
            width: 0%;
            transition: width 0.5s ease;
        }

        .mr-loading {
            text-align: center;
            padding: 20px;
            color: #666;
        }
    `;

    GM_addStyle(styles);

    // 搜索配置
    const SEARCH_CONFIG = {
        maxPoints: 90,
        pointsPerSearch: 3,
        pc: {
            minDelay: 60000, // 60秒
            maxDelay: 120000 // 120秒
        },
        mobile: {
            minDelay: 25000, // 25秒
            maxDelay: 50000  // 50秒
        }
    };

    let searchWindow = null;
    let g_remainingSearches = Math.ceil(SEARCH_CONFIG.maxPoints / SEARCH_CONFIG.pointsPerSearch);
    let g_maxSearches = Math.ceil(SEARCH_CONFIG.maxPoints / SEARCH_CONFIG.pointsPerSearch);
    let g_searchStatusText = '';

    // 生成随机搜索词
    function generateRandomQuery() {
        const keywords = ["weather", "news", "sports", "finance", "movies", "tech", "food", "travel", "music", "art", "history", "science", "nature", "space", "cars", "games", "books", "fashion", "health", "fitness"];
        const keyword = keywords[Math.floor(Math.random() * keywords.length)];
        const randomString = Math.random().toString(36).substring(7);
        return `${keyword} ${randomString}`;
    }

    // 开始搜索任务
    function startSearchTask() {
        const deviceType = getDeviceType();
        
        // 动态计算搜索次数
        let totalSearches = g_remainingSearches;
        
        let currentSearch = 0;
        const btn = document.getElementById('mr-start-search');
        
        function setStatus(text) {
            g_searchStatusText = text;
            let statusEl = document.getElementById('mr-search-timer');
            if(statusEl) statusEl.innerText = text;
        }

        if(btn) btn.disabled = true;
        setStatus(`准备...`);

        // 移动端不设置窗口大小,避免被识别为桌面弹窗
        let windowFeatures = 'width=500,height=500';
        if (deviceType === "手机" || deviceType === "平板") {
            searchWindow = window.open('https://www.bing.com', '_blank');
        } else {
            searchWindow = window.open('https://www.bing.com', '_blank', windowFeatures);
        }
        
        if (!searchWindow) {
            alert('请允许弹出窗口以进行自动搜索');
            if(btn) btn.disabled = false;
            return;
        }

        function doSearch() {
            if (currentSearch >= totalSearches) {
                setStatus('完成');
                if(btn) btn.disabled = false;
                if(searchWindow) searchWindow.close();
                return;
            }

            currentSearch++;
            const query = generateRandomQuery();
            setStatus(`搜索中...`);
            
            if(searchWindow && !searchWindow.closed) {
                try {
                    const doc = searchWindow.document;
                    const input = doc.getElementById('sb_form_q');
                    const form = doc.getElementById('sb_form');

                    if (input && form) {
                        input.value = query;
                        const event = new Event('input', { bubbles: true });
                        input.dispatchEvent(event);
                        
                        const submitBtn = doc.getElementById('sb_form_go');
                        if (submitBtn) {
                            submitBtn.click();
                        } else {
                            form.submit();
                        }
                    }
                } catch (e) {
                    console.error("搜索出错", e);
                }
            } else {
                setStatus('停止');
                if(btn) btn.disabled = false;
                return;
            }

            // 根据设备类型设置延迟
            let minDelay, maxDelay;
            if (deviceType === "手机" || deviceType === "平板") {
                minDelay = SEARCH_CONFIG.mobile.minDelay;
                maxDelay = SEARCH_CONFIG.mobile.maxDelay;
            } else {
                minDelay = SEARCH_CONFIG.pc.minDelay;
                maxDelay = SEARCH_CONFIG.pc.maxDelay;
            }

            const delay = Math.floor(Math.random() * (maxDelay - minDelay + 1)) + minDelay;
            
            // 倒计时逻辑
            let remainingSeconds = Math.ceil(delay / 1000);
            const updateStatus = () => {
                setStatus(`等待 ${remainingSeconds}秒`);
            };
            updateStatus();

            const timer = setInterval(() => {
                if (!searchWindow || searchWindow.closed) {
                    clearInterval(timer);
                    setStatus('停止');
                    if(btn) btn.disabled = false;
                    return;
                }

                remainingSeconds--;
                if (remainingSeconds <= 0) {
                    clearInterval(timer);
                    doSearch();
                } else {
                    updateStatus();
                }
            }, 1000);
        }

        // 首次延迟执行
        setTimeout(doSearch, 2000);
    }

    // UI 构建
    function createUI() {
        const container = document.createElement('div');
        container.id = 'mr-dashboard-container';
        
        const toggle = document.createElement('div');
        toggle.id = 'mr-dashboard-toggle';
        toggle.innerText = 'Rewards';
        toggle.onclick = () => {
            container.classList.toggle('expanded');
        };

        const panel = document.createElement('div');
        panel.id = 'mr-dashboard-panel';

        // 内容区域
        const content = document.createElement('div');
        content.id = 'mr-dashboard-content';
        panel.appendChild(content);

        // 控制区域
        const controls = document.createElement('div');
        controls.className = 'mr-section';
        controls.style.marginTop = '10px';
        controls.style.borderTop = '1px solid #eee';
        controls.style.paddingTop = '10px';

        const btn = document.createElement('button');
        btn.id = 'mr-start-search';
        btn.innerText = '开始自动搜索';
        btn.style.width = '100%';
        btn.style.padding = '8px';
        btn.style.backgroundColor = '#0078d4';
        btn.style.color = 'white';
        btn.style.border = 'none';
        btn.style.borderRadius = '4px';
        btn.style.cursor = 'pointer';
        btn.onclick = startSearchTask;

        controls.appendChild(btn);
        panel.appendChild(controls);

        container.appendChild(toggle);
        container.appendChild(panel);
        document.body.appendChild(container);
    }

    // 数据获取
    function fetchData() {
        const content = document.getElementById('mr-dashboard-content');
        // 仅在首次加载或无内容时显示 loading,避免自动刷新闪烁
        if (!content.querySelector('.mr-section')) {
            content.innerHTML = '<div class="mr-loading">加载中...</div>';
        }

        const timestamp = new Date().getTime();
        const url = `${API_URL}&_=${timestamp}`;

        GM_xmlhttpRequest({
            method: "GET",
            url: url,
            headers: {
                "X-Requested-With": "XMLHttpRequest"
            },
            onload: function(response) {
                try {
                    const data = JSON.parse(response.responseText);
                    renderData(data);
                } catch (e) {
                    console.error("解析 Rewards 数据失败", e);
                    content.innerHTML = `<div style="color:red">数据解析失败: ${e.message}</div>`;
                }
            },
            onerror: function(err) {
                console.error("获取 Rewards 数据失败", err);
                content.innerHTML = '<div style="color:red">网络请求失败</div>';
            }
        });
    }

    // 检测设备类型
    function getDeviceType() {
        const ua = navigator.userAgent;
        if (/(tablet|ipad|playbook|silk)|(android(?!.*mobi))/i.test(ua)) {
            return "平板";
        }
        if (/Mobile|Android|iP(hone|od)|IEMobile|BlackBerry|Kindle|Silk-Accelerated|(hpw|web)OS|Opera M(obi|ini)/.test(ua)) {
            return "手机";
        }
        return "电脑";
    }

    // 数据渲染
    function renderData(data) {
        const content = document.getElementById('mr-dashboard-content');
        const dashboard = data.dashboard || data;
        
        // 获取设备类型
        const deviceType = getDeviceType();

        // 1. 积分等级
        let levelName = "未知";
        let totalPoints = 0;
        let todayPoints = 0;
        
        if (dashboard.userStatus) {
            totalPoints = dashboard.userStatus.availablePoints || 0;
            if (dashboard.userStatus.levelInfo) {
                levelName = dashboard.userStatus.levelInfo.activeLevel || dashboard.userStatus.levelInfo.level || "未知";
            }
        }

        // 2. 每日积分进度
        let pcSearchCurrent = 0, pcSearchMax = 0;
        let mobileSearchCurrent = 0, mobileSearchMax = 0;

        // 解析 Counters
        if (dashboard.userStatus && dashboard.userStatus.counters) {
            const counters = dashboard.userStatus.counters;
            
            // 电脑搜索
            if (counters.pcSearch && counters.pcSearch.length > 0) {
                counters.pcSearch.forEach(item => {
                    pcSearchCurrent += item.pointProgress || 0;
                    pcSearchMax += item.pointMax || item.pointProgressMax || 0;
                });
            }
            
            // 移动搜索
            if (counters.mobileSearch && counters.mobileSearch.length > 0) {
                counters.mobileSearch.forEach(item => {
                    mobileSearchCurrent += item.pointProgress || 0;
                    mobileSearchMax += item.pointMax || item.pointProgressMax || 0;
                });
            }
        }

        // 计算今日总分
        todayPoints = pcSearchCurrent + mobileSearchCurrent;
        let todayMaxPoints = pcSearchMax + mobileSearchMax;

        // 计算剩余搜索次数
        let currentTypePoints = 0;
        let maxTypePoints = 0;
        if (deviceType === "电脑") {
            currentTypePoints = pcSearchCurrent;
            maxTypePoints = pcSearchMax;
        } else {
            currentTypePoints = mobileSearchCurrent;
            maxTypePoints = mobileSearchMax;
        }
        
        const remainingPoints = Math.max(0, maxTypePoints - currentTypePoints);
        const remainingSearches = Math.ceil(remainingPoints / SEARCH_CONFIG.pointsPerSearch);
        g_remainingSearches = remainingSearches;
        const totalSearchesNeeded = Math.ceil(maxTypePoints / SEARCH_CONFIG.pointsPerSearch);
        g_maxSearches = totalSearchesNeeded;
        const searchProgress = totalSearchesNeeded > 0 ? ((totalSearchesNeeded - remainingSearches) / totalSearchesNeeded) * 100 : 0;

        // 构建 HTML
        let html = `
            <div class="mr-section">
                <div class="mr-title">仪表盘</div>
                <div class="mr-item"><span>当前设备</span> <span>${deviceType}</span></div>
                <div class="mr-item"><span>当前等级</span> <span>${levelName}</span></div>
                <div class="mr-item"><span>总积分</span> <span>${totalPoints}</span></div>
                
                <div class="mr-item"><span>今日获取</span> <span>${todayPoints} / ${todayMaxPoints}</span></div>
                <div class="mr-progress-bar"><div class="mr-progress-fill" style="width: ${(todayMaxPoints > 0 ? todayPoints/todayMaxPoints*100 : 0)}%"></div></div>
                
                <div class="mr-item" style="margin-top: 8px;"><span>剩余搜索次数</span> <span>${remainingSearches} <span id="mr-search-timer" style="margin-left: 8px; color: #0078d4;">${g_searchStatusText}</span></span></div>
                <div class="mr-progress-bar"><div class="mr-progress-fill" style="width: ${searchProgress}%"></div></div>
                
                <div style="margin-top: 8px; padding-left: 10px; border-left: 2px solid #eee;">
                    <div class="mr-item" style="font-size: 12px; color: #666;"><span>电脑搜索</span> <span>${pcSearchCurrent} / ${pcSearchMax}</span></div>
                    <div class="mr-progress-bar" style="height: 4px;"><div class="mr-progress-fill" style="width: ${(pcSearchMax > 0 ? pcSearchCurrent/pcSearchMax*100 : 0)}%"></div></div>
                    
                    <div class="mr-item" style="font-size: 12px; color: #666; margin-top: 4px;"><span>移动搜索</span> <span>${mobileSearchCurrent} / ${mobileSearchMax}</span></div>
                    <div class="mr-progress-bar" style="height: 4px;"><div class="mr-progress-fill" style="width: ${(mobileSearchMax > 0 ? mobileSearchCurrent/mobileSearchMax*100 : 0)}%"></div></div>
                </div>
            </div>
        `;

        content.innerHTML = html;
    }

    // 初始化
    window.addEventListener('load', () => {
        createUI();
        fetchData();
        setInterval(fetchData, 30000);
    });

})();