// ==UserScript== // @name Microsoft Rewards 全能自动化助手 // @namespace https://scriptcat.org/zh-CN/ // @version 1.1.1 // @description 自动完成Bing Rewards每日任务:每日活动、App签到、阅读赚取、PC搜索。支持macOS液态玻璃悬浮窗、自定义任务顺序、随机执行、进度保存、积分显示、自动暂停防封。 // @author YourName // @license MIT // @match https://cn.bing.com/* // @match https://rewards.bing.com/* // @match https://login.live.com/oauth20_desktop.srf* // @grant GM_xmlhttpRequest // @grant GM_setValue // @grant GM_getValue // @grant GM_addStyle // @grant GM_notification // @grant GM_registerMenuCommand // @grant GM_unregisterMenuCommand // @connect prod.rewardsplatform.microsoft.com // @connect rewards.bing.com // @connect login.live.com // @connect api.bilibili.com // @connect * // @run-at document-end // ==/UserScript== (function () { 'use strict'; const DAPI_BASE = 'https://prod.rewardsplatform.microsoft.com/dapi'; const CLIENT_ID = '0000000040170455'; const REDIRECT_URI = 'https://login.live.com/oauth20_desktop.srf'; const SCOPE = 'service::prod.rewardsplatform.microsoft.com::MBI_SSL'; const PAUSE_EVERY_N_SEARCHES = 5; const PAUSE_DURATION_MS = 10 * 60 * 1000; const STORAGE_KEYS = { ACCESS_TOKEN: 'mr_access_token', REFRESH_TOKEN: 'mr_refresh_token', EXPIRES_AT: 'mr_expires_at', SETTINGS: 'mr_settings', TASK_ORDER: 'mr_task_order', PROGRESS: 'mr_progress', }; // ★ 移除 theme 设置项(固定 macOS 液态玻璃) const DEFAULT_SETTINGS = { searchStayMin: 2, readStayMin: 2, maxSearches: 30, randomOrder: false, authorizeAppCheckin: false, authorizeAppRead: false, enableDailySet: true, enableAppCheckin: true, enableAppRead: true, enableMoreActivities: true, enablePCSearch: true, }; const DEFAULT_TASK_ORDER = [ 'dailySet', 'appCheckin', 'appRead', 'moreActivities', 'pcSearch', ]; const TASK_LABELS = { dailySet: '每日任务', appCheckin: '每日签到', appRead: '每日阅读', moreActivities: '更多活动', pcSearch: 'PC搜索', }; const TASKS_REQUIRING_AUTH = { appCheckin: 'authorizeAppCheckin', appRead: 'authorizeAppRead', }; // ★ 固定 macOS 液态玻璃配色 const THEME = { bg: 'rgba(255,255,255,0.72)', textColor: '#1d1d1f', borderColor: 'rgba(0,0,0,0.08)', cardBg: 'rgba(255,255,255,0.5)', cardHover: 'rgba(0,0,0,0.04)', headerBg: 'rgba(255,255,255,0.4)', }; // ==================== 工具函数 ==================== const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); const rand = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min; function shuffleArray(arr) { const a = [...arr]; for (let i = a.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [a[i], a[j]] = [a[j], a[i]]; } return a; } function todayStr() { const d = new Date(); return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`; } function formatDuration(ms) { if (ms <= 0) return '0秒'; const s = Math.floor(ms / 1000); const m = Math.floor(s / 60); const sec = s % 60; if (m >= 60) { const h = Math.floor(m / 60); return `${h}小时${m % 60}分`; } return m > 0 ? `${m}分${sec}秒` : `${sec}秒`; } function log(msg) { console.log(`[Rewards助手] ${msg}`); const logEl = document.getElementById('mr-log-area'); if (logEl) { const line = document.createElement('div'); line.textContent = `[${new Date().toLocaleTimeString()}] ${msg}`; logEl.appendChild(line); logEl.scrollTop = logEl.scrollHeight; } } // ==================== 存储管理 ==================== function getSettings() { return { ...DEFAULT_SETTINGS, ...GM_getValue(STORAGE_KEYS.SETTINGS, {}) }; } function saveSettings(s) { GM_setValue(STORAGE_KEYS.SETTINGS, s); } function getTaskOrder() { const stored = GM_getValue(STORAGE_KEYS.TASK_ORDER, [...DEFAULT_TASK_ORDER]); const filtered = stored.filter((id) => DEFAULT_TASK_ORDER.includes(id)); DEFAULT_TASK_ORDER.forEach((id) => { if (!filtered.includes(id)) filtered.push(id); }); return filtered; } function saveTaskOrder(o) { GM_setValue(STORAGE_KEYS.TASK_ORDER, o); } function getProgress() { const p = GM_getValue(STORAGE_KEYS.PROGRESS, null); if (!p || p.date !== todayStr()) { return { date: todayStr(), completedTasks: [], searchCount: 0, lastPauseAt: 0, pausedUntil: 0, todayPoints: 0, lastBalance: 0, }; } return p; } function saveProgress(p) { GM_setValue(STORAGE_KEYS.PROGRESS, p); } function clearProgress() { GM_setValue(STORAGE_KEYS.PROGRESS, null); } // ==================== OAuth ==================== function buildAuthUrl() { return ( `https://login.live.com/oauth20_authorize.srf?client_id=${CLIENT_ID}` + `&scope=${encodeURIComponent(SCOPE)}` + `&response_type=code` + `&redirect_uri=${encodeURIComponent(REDIRECT_URI)}` + `&display=touch` ); } function captureAuthCode() { const url = new URL(location.href); const code = url.searchParams.get('code'); if (code) { GM_setValue('mr_auth_code', code); log('已捕获授权码,正在交换Token...'); exchangeCodeForToken(code); } } function exchangeCodeForToken(code) { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'POST', url: 'https://login.live.com/oauth20_token.srf', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, data: `client_id=${CLIENT_ID}` + `&redirect_uri=${encodeURIComponent(REDIRECT_URI)}` + `&code=${code}` + `&grant_type=authorization_code` + `&scope=${encodeURIComponent(SCOPE)}`, onload(resp) { try { const data = JSON.parse(resp.responseText); if (data.access_token) { GM_setValue(STORAGE_KEYS.ACCESS_TOKEN, data.access_token); if (data.refresh_token) GM_setValue(STORAGE_KEYS.REFRESH_TOKEN, data.refresh_token); GM_setValue(STORAGE_KEYS.EXPIRES_AT, Date.now() + (data.expires_in || 3600) * 1000); log('Token获取成功'); GM_notification({ text: 'Rewards授权成功!', title: 'Rewards助手' }); resolve(data.access_token); } else { log('Token交换失败: ' + resp.responseText); reject(new Error('Token exchange failed')); } } catch (e) { reject(e); } }, onerror: reject, }); }); } function refreshAccessToken() { const refreshToken = GM_getValue(STORAGE_KEYS.REFRESH_TOKEN, ''); if (!refreshToken) return Promise.reject(new Error('无refresh_token')); return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'POST', url: 'https://login.live.com/oauth20_token.srf', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, data: `client_id=${CLIENT_ID}` + `&redirect_uri=${encodeURIComponent(REDIRECT_URI)}` + `&refresh_token=${refreshToken}` + `&grant_type=refresh_token` + `&scope=${encodeURIComponent(SCOPE)}`, onload(resp) { try { const data = JSON.parse(resp.responseText); if (data.access_token) { GM_setValue(STORAGE_KEYS.ACCESS_TOKEN, data.access_token); if (data.refresh_token) GM_setValue(STORAGE_KEYS.REFRESH_TOKEN, data.refresh_token); GM_setValue(STORAGE_KEYS.EXPIRES_AT, Date.now() + (data.expires_in || 3600) * 1000); resolve(data.access_token); } else reject(new Error('刷新失败')); } catch (e) { reject(e); } }, onerror: reject, }); }); } async function getValidToken() { let token = GM_getValue(STORAGE_KEYS.ACCESS_TOKEN, ''); const expiresAt = GM_getValue(STORAGE_KEYS.EXPIRES_AT, 0); if (!token || Date.now() > expiresAt - 60000) { try { token = await refreshAccessToken(); } catch (e) { log('Token刷新失败,需要重新授权'); return null; } } return token; } // ==================== Rewards API ==================== function apiRequest(method, path, token, body = null) { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method, url: `${DAPI_BASE}${path}`, headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json', 'X-Rewards-Country': 'cn', 'X-Rewards-Language': 'zh', }, data: body ? JSON.stringify(body) : undefined, onload(resp) { if (resp.status >= 200 && resp.status < 300) { try { resolve(JSON.parse(resp.responseText)); } catch (e) { resolve({}); } } else if (resp.status === 401) reject(new Error('AUTH_EXPIRED')); else reject(new Error(`API ${resp.status}`)); }, onerror: reject, }); }); } async function fetchUserData(token) { const resp = await apiRequest('GET', '/me?channel=SAAndroid&options=511', token); return resp.response || {}; } async function completeActivity(token, offerId) { return apiRequest('POST', '/me/activities', token, { type: 101, attributes: { offerid: offerId }, }); } async function fetchPoints(token) { const data = await fetchUserData(token); const total = parseInt(data.balance || 0) || 0; return { total, todayEarned: data.dailyPoints ?? null }; } // ==================== 任务执行引擎 ==================== class TaskRunner { constructor() { this.running = false; this.token = null; this.settings = getSettings(); this.baseOrder = getTaskOrder(); this.taskOrder = this.settings.randomOrder ? shuffleArray(this.baseOrder) : [...this.baseOrder]; this.stats = { completed: 0, failed: 0, points: 0 }; this.openedWindows = []; this.allDone = false; this.progress = getProgress(); } stop() { this.running = false; this.closeAllWindows(); } closeAllWindows() { this.openedWindows.forEach((w) => { try { if (w && !w.closed) w.close(); } catch (e) {} }); this.openedWindows = []; } async wait(ms) { const end = Date.now() + ms; while (Date.now() < end) { if (!this.running) return false; const remaining = end - Date.now(); if (remaining > 3000) updateCountdown(remaining); await sleep(Math.min(500, remaining)); } updateCountdown(0); return this.running; } async waitUntil(timestamp) { while (Date.now() < timestamp) { if (!this.running) return false; const remaining = timestamp - Date.now(); updateCountdown(remaining); await sleep(Math.min(1000, remaining)); } updateCountdown(0); return this.running; } openTracked(url) { const w = window.open(url, '_blank'); if (w) this.openedWindows.push(w); return w; } async init() { this.token = await getValidToken(); if (!this.token) { log('❌ 无有效Token,正在打开授权页...'); window.open(buildAuthUrl(), '_blank'); return false; } log('✅ Token有效,准备执行任务'); await this.refreshPoints(); return true; } async refreshPoints() { try { const pts = await fetchPoints(this.token); uiState.totalPoints = pts.total; if (pts.todayEarned !== null) uiState.todayPoints = pts.todayEarned; updateUI(); } catch (e) { console.warn('[Rewards助手] 积分刷新失败:', e.message); } } async runAll() { this.running = true; const ok = await this.init(); if (!ok) { this.running = false; this.closeAllWindows(); return; } const enabledTasks = this.taskOrder.filter((t) => { const k = 'enable' + t.charAt(0).toUpperCase() + t.slice(1); return this.settings[k]; }); uiState.totalTasks = enabledTasks.length; log(`📋 本次执行顺序:${enabledTasks.map((t) => TASK_LABELS[t]).join(' → ')}` + (this.settings.randomOrder ? '(随机)' : '')); const pendingTasks = this.taskOrder.filter((t) => { if (this.progress.completedTasks.includes(t)) return false; const k = 'enable' + t.charAt(0).toUpperCase() + t.slice(1); if (!this.settings[k]) return false; const authKey = TASKS_REQUIRING_AUTH[t]; if (authKey && !this.settings[authKey]) return false; return true; }); if (this.progress.completedTasks.length > 0) { log(`🔄 检测到历史进度,已完成 ${this.progress.completedTasks.length} 个任务,续接执行`); } if (this.progress.pausedUntil > Date.now()) { const remain = this.progress.pausedUntil - Date.now(); log(`⏸ 检测到防封暂停,剩余 ${formatDuration(remain)}`); if (!await this.waitUntil(this.progress.pausedUntil)) { this.running = false; this.closeAllWindows(); return; } log('▶ 暂停结束,继续执行'); } for (const taskId of pendingTasks) { if (!this.running) break; log(`▶ 开始执行:${TASK_LABELS[taskId]}`); try { await this.executeTask(taskId); } catch (e) { log(`⚠ ${TASK_LABELS[taskId]} 异常: ${e.message}`); } if (!this.progress.completedTasks.includes(taskId)) { this.progress.completedTasks.push(taskId); saveProgress(this.progress); } uiState.completedTasks = this.progress.completedTasks.length; uiState.stats = this.stats; updateUI(); if (!this.running) break; await this.wait(2000); } if (this.running) { await this.refreshPoints(); await this.checkAllCompleted(); const msg = this.allDone ? `🎉 所有任务已完成!成功 ${this.stats.completed},失败 ${this.stats.failed},今日积分 +${uiState.todayPoints}` : `🎉 本轮任务执行完毕,成功 ${this.stats.completed},失败 ${this.stats.failed}`; log(msg); GM_notification({ text: msg, title: 'Rewards助手 - 完成通知', timeout: 10000, }); } else { log('⏹ 任务已被用户停止'); } this.running = false; this.closeAllWindows(); } async checkAllCompleted() { try { const data = await fetchUserData(this.token); const promos = data.promotions || []; const pending = promos.filter((p) => { const attr = p.attributes || {}; if (attr.complete) return false; return ['urlreward', 'checkin', 'msnreadearn', 'search'].includes(attr.type) || !!p.destination; }); this.allDone = pending.length === 0; if (this.allDone) log(' ✓ 所有任务均已标记完成'); else log(` ℹ 仍有 ${pending.length} 个未完成任务`); } catch (e) { log(' ⚠ 完成状态检查失败: ' + e.message); this.allDone = false; } } async executeTask(taskId) { switch (taskId) { case 'dailySet': await this.doDailySet(); break; case 'appCheckin': await this.doAppCheckin(); break; case 'appRead': await this.doAppRead(); break; case 'moreActivities': await this.doMoreActivities(); break; case 'pcSearch': await this.doPCSearch(); break; } } async doDailySet() { const data = await fetchUserData(this.token); const promos = (data.promotions || []).filter( (p) => p.attributes?.type === 'urlreward' && !p.attributes?.complete ); if (promos.length === 0) { log(' - 每日任务已完成'); return; } for (const p of promos) { if (!this.running) return; const offerId = p.attributes?.offerid; if (!offerId) continue; try { await completeActivity(this.token, offerId); this.stats.completed++; this.stats.points += parseInt(p.attributes?.points || '0'); log(` ✓ 完成: ${p.name || offerId}`); } catch (e) { this.stats.failed++; } if (!await this.wait(rand(2000, 4000))) return; } } async doAppCheckin() { const data = await fetchUserData(this.token); const checkin = (data.promotions || []).find( (p) => p.attributes?.type === 'checkin' && !p.attributes?.complete ); if (checkin) { try { await completeActivity(this.token, checkin.attributes?.offerid); this.stats.completed++; log(' ✓ 每日签到完成'); } catch (e) { log(' ⚠ 每日签到失败: ' + e.message); } } else log(' - 每日签到已完成或不存在'); } async doAppRead() { const data = await fetchUserData(this.token); const reads = (data.promotions || []).filter( (p) => p.attributes?.type === 'msnreadearn' && !p.attributes?.complete ); for (const r of reads) { if (!this.running) return; const url = r.destination || r.attributes?.destination; if (url) { this.openTracked(url); log(` 📖 阅读停留 ${this.settings.readStayMin} 分钟...`); if (!await this.wait(this.settings.readStayMin * 60 * 1000)) return; } try { await completeActivity(this.token, r.attributes?.offerid); this.stats.completed++; log(` ✓ 阅读完成: ${r.name || '文章'}`); } catch (e) { this.stats.failed++; log(` ⚠ 阅读失败: ${e.message}`); } if (!await this.wait(rand(3000, 6000))) return; } if (reads.length === 0) log(' - 阅读任务已完成或不存在'); } async doMoreActivities() { const data = await fetchUserData(this.token); const acts = (data.promotions || []).filter( (p) => !p.attributes?.complete && !['urlreward', 'checkin', 'msnreadearn', 'search'].includes(p.attributes?.type) && p.destination ); for (const a of acts) { if (!this.running) return; const url = a.destination || a.attributes?.destination; if (url) { this.openTracked(url); if (!await this.wait(60 * 1000)) return; } try { await completeActivity(this.token, a.attributes?.offerid); this.stats.completed++; log(` ✓ 完成活动: ${a.name || a.attributes?.offerid}`); } catch (e) { this.stats.failed++; } if (!await this.wait(rand(3000, 5000))) return; } } async doPCSearch() { const data = await fetchUserData(this.token); const pcPromo = (data.promotions || []).find( (p) => p.attributes?.type === 'search' && p.attributes?.classificationTag === 'PCSearch' ); if (!pcPromo) { log(' - 未找到PC搜索任务'); return; } const pcCounter = pcPromo.attributes?.pcCounter || pcPromo.attributes; const max = parseInt(pcCounter?.originalmax || pcCounter?.max || this.settings.maxSearches); const progress = parseInt(pcCounter?.progress || 0); let remaining = Math.max(0, max - progress); remaining = Math.max(0, remaining - this.progress.searchCount); if (remaining === 0) { log(' - PC搜索已完成'); return; } log(` - PC搜索: ${progress + this.progress.searchCount}/${max},本次还需 ${remaining} 次`); const queries = await getSearchQueries(remaining); for (let i = 0; i < queries.length; i++) { if (!this.running) return; if (this.progress.searchCount > 0 && this.progress.searchCount % PAUSE_EVERY_N_SEARCHES === 0 && this.progress.lastPauseAt !== this.progress.searchCount) { const pauseUntil = Date.now() + PAUSE_DURATION_MS; this.progress.pausedUntil = pauseUntil; this.progress.lastPauseAt = this.progress.searchCount; saveProgress(this.progress); log(` ⏸ 已完成 ${this.progress.searchCount} 次搜索,自动暂停 ${PAUSE_DURATION_MS / 60000} 分钟防封...`); GM_notification({ text: `已搜索 ${this.progress.searchCount} 次,暂停 ${PAUSE_DURATION_MS / 60000} 分钟`, title: 'Rewards助手 - 防封暂停', }); if (!await this.waitUntil(pauseUntil)) return; this.progress.pausedUntil = 0; saveProgress(this.progress); log(' ▶ 暂停结束,继续搜索'); } const q = queries[i]; log(` 🔍 搜索 (${this.progress.searchCount + 1}): "${q}"`); this.openTracked(`https://cn.bing.com/search?q=${encodeURIComponent(q)}&form=QBLH`); this.stats.completed++; this.progress.searchCount++; saveProgress(this.progress); if (i < queries.length - 1) { log(` ⏳ 停留 ${this.settings.searchStayMin} 分钟后继续...`); if (!await this.wait(this.settings.searchStayMin * 60 * 1000)) return; } } } } // ==================== 搜索热词 ==================== async function getSearchQueries(count) { const queries = []; try { const hotwords = await fetchHotwords(); queries.push(...hotwords); } catch (e) { log('热搜获取失败,使用内置词库'); } const builtin = [ '今日新闻', '天气预报', '科技动态', '健康养生', '电影推荐', '旅游攻略', '美食菜谱', '股票行情', '人工智能', '编程教程', '历史故事', '音乐推荐', '体育赛事', '游戏攻略', '学习方法', '职场技能', '理财知识', '育儿经验', '汽车评测', '数码产品', ]; const shuffled = builtin.sort(() => Math.random() - 0.5); while (queries.length < count) { queries.push(shuffled[queries.length % shuffled.length]); } return [...new Set(queries)].slice(0, count); } async function fetchHotwords() { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'GET', url: 'https://api.bilibili.com/x/web-interface/search/square?limit=20', headers: { 'User-Agent': 'Mozilla/5.0' }, timeout: 8000, onload(resp) { try { const data = JSON.parse(resp.responseText); const list = data?.data?.trending?.list || []; resolve(list.map((item) => item.keyword).filter(Boolean)); } catch (e) { reject(e); } }, onerror: reject, ontimeout: reject, }); }); } // ==================== 悬浮窗 UI ==================== let uiState = { running: false, stats: { completed: 0, failed: 0, points: 0 }, totalTasks: 0, completedTasks: 0, totalPoints: 0, todayPoints: 0, countdown: 0, }; let currentRunner = null; let floatVisible = true; let menuCommandId = null; function updateUI() { const btn = document.getElementById('mr-btn-start'); if (btn) { btn.textContent = uiState.running ? '⏹ 停止执行' : '▶ 开始执行'; btn.style.background = uiState.running ? '#e74c3c' : '#0078d4'; } const progressBar = document.getElementById('mr-progress-bar'); const progressText = document.getElementById('mr-progress-text'); if (progressBar && progressText) { const total = uiState.totalTasks || 1; const done = Math.min(uiState.completedTasks, total); const pct = Math.round((done / total) * 100); progressBar.style.width = pct + '%'; progressText.textContent = `${done} / ${total} 任务完成 (${pct}%)`; } const statsEl = document.getElementById('mr-stats'); if (statsEl) { statsEl.innerHTML = `✅ ${uiState.stats.completed} | ❌ ${uiState.stats.failed} | ` + `💰 今日 +${uiState.todayPoints} | 🏆 总 ${uiState.totalPoints}`; } } function updateCountdown(ms) { uiState.countdown = ms; const el = document.getElementById('mr-countdown'); if (el) { if (ms <= 0) el.textContent = ''; else el.textContent = `⏳ 等待中:${formatDuration(ms)}`; } } function toggleFloatPanel() { const el = document.getElementById('mr-float-container'); if (!el) { log('⚠ 悬浮面板不存在,请刷新页面'); return; } floatVisible = !floatVisible; el.style.display = floatVisible ? '' : 'none'; try { if (menuCommandId !== null && typeof GM_unregisterMenuCommand === 'function') { GM_unregisterMenuCommand(menuCommandId); } } catch (e) {} try { const newText = floatVisible ? '👁 隐藏悬浮面板' : '👁 显示悬浮面板'; menuCommandId = GM_registerMenuCommand(newText, toggleFloatPanel); } catch (e) {} } function registerMenuCommands() { try { menuCommandId = GM_registerMenuCommand( floatVisible ? '👁 隐藏悬浮面板' : '👁 显示悬浮面板', toggleFloatPanel ); GM_registerMenuCommand('🔄 重置今日任务进度', () => { clearProgress(); log('已重置今日任务进度'); GM_notification({ text: '今日任务进度已重置', title: 'Rewards助手' }); // 重置后立即刷新UI uiState.completedTasks = 0; uiState.totalPoints = 0; uiState.todayPoints = 0; updateUI(); rebuildFloatWindow(); }); } catch (e) { console.warn('[Rewards助手] 菜单注册失败:', e); } } // ★ 重建悬浮窗(保留日志、位置、运行状态) function rebuildFloatWindow() { const oldContainer = document.getElementById('mr-float-container'); if (!oldContainer) { createFloatWindow(); return; } const oldLog = document.getElementById('mr-log-area'); const logHTML = oldLog ? oldLog.innerHTML : ''; const rect = oldContainer.getBoundingClientRect(); const wasLeft = oldContainer.style.left; const wasTop = oldContainer.style.top; const wasRight = oldContainer.style.right; oldContainer.remove(); createFloatWindow(); const newLog = document.getElementById('mr-log-area'); if (newLog && logHTML) { newLog.innerHTML = logHTML; newLog.scrollTop = newLog.scrollHeight; } // 恢复位置 const newContainer = document.getElementById('mr-float-container'); if (newContainer) { if (wasLeft) { newContainer.style.left = wasLeft; newContainer.style.top = wasTop; newContainer.style.right = 'auto'; } else if (rect && rect.left !== 20) { // 有手动拖动过 newContainer.style.left = rect.left + 'px'; newContainer.style.top = rect.top + 'px'; newContainer.style.right = 'auto'; } } updateUI(); } function createFloatWindow() { const settings = getSettings(); const { bg, textColor, borderColor, cardBg, cardHover, headerBg } = THEME; GM_addStyle(` #mr-float-container { position: fixed; top: 20px; right: 20px; z-index: 999999; width: 380px; max-height: calc(100vh - 40px); display: flex; flex-direction: column; font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Helvetica Neue", sans-serif; font-size: 13px; color: ${textColor}; background: ${bg}; backdrop-filter: blur(30px) saturate(180%); -webkit-backdrop-filter: blur(30px) saturate(180%); border: 1px solid ${borderColor}; border-radius: 14px; box-shadow: 0 8px 40px rgba(0,0,0,0.18), 0 2px 8px rgba(0,0,0,0.08); overflow: hidden; transition: all 0.3s ease; user-select: none; } #mr-float-header { flex-shrink: 0; display: flex; align-items: center; justify-content: space-between; padding: 10px 14px; cursor: move; background: ${headerBg}; border-bottom: 1px solid ${borderColor}; } #mr-float-header .title { font-weight: 600; font-size: 14px; } #mr-float-header .close-btn { cursor: pointer; opacity: 0.6; font-size: 16px; line-height: 1; padding: 2px 6px; border-radius: 6px; } #mr-float-header .close-btn:hover { opacity: 1; background: rgba(128,128,128,0.15); } #mr-float-body { padding: 12px 14px; overflow-y: auto; overflow-x: hidden; flex: 1 1 auto; min-height: 0; } #mr-float-body::-webkit-scrollbar { width: 5px; } #mr-float-body::-webkit-scrollbar-thumb { background: rgba(128,128,128,0.3); border-radius: 3px; } #mr-progress-wrap { padding: 8px 10px; background: ${cardBg}; border-radius: 8px; margin-bottom: 8px; } #mr-progress-text { font-size: 11px; opacity: 0.75; margin-bottom: 6px; text-align: center; } #mr-progress-track { height: 6px; background: rgba(0,0,0,0.08); border-radius: 3px; overflow: hidden; } #mr-progress-bar { height: 100%; width: 0%; background: linear-gradient(90deg, #0078d4, #28a745); border-radius: 3px; transition: width 0.5s ease; } #mr-countdown { font-size: 11px; text-align: center; padding: 4px 0; color: #ff9500; font-weight: 500; min-height: 16px; } #mr-log-area { height: 110px; overflow-y: auto; font-size: 11px; line-height: 1.6; padding: 8px; margin: 8px 0; background: ${cardBg}; border-radius: 8px; font-family: "SF Mono", Menlo, Consolas, monospace; scrollbar-width: thin; } #mr-log-area::-webkit-scrollbar { width: 4px; } #mr-log-area::-webkit-scrollbar-thumb { background: rgba(128,128,128,0.3); border-radius: 2px; } #mr-stats { font-size: 11px; opacity: 0.85; text-align: center; padding: 4px 0; } .mr-btn { display: inline-flex; align-items: center; justify-content: center; padding: 8px 16px; border: none; border-radius: 8px; font-size: 13px; font-weight: 500; cursor: pointer; transition: all 0.2s ease; width: 100%; background: #0078d4; color: #fff; } .mr-btn:hover { filter: brightness(1.1); transform: translateY(-1px); } .mr-btn:active { transform: translateY(0); } .mr-btn-secondary { background: ${cardBg}; color: ${textColor}; margin-top: 6px; border: 1px solid ${borderColor}; } .mr-btn-secondary:hover { background: ${cardHover}; } .mr-setting-row { display: flex; align-items: center; justify-content: space-between; padding: 6px 0; } .mr-setting-row label { font-size: 12px; opacity: 0.8; } .mr-setting-row input[type="number"] { width: 60px; padding: 4px 8px; border: 1px solid ${borderColor}; border-radius: 6px; background: ${cardBg}; color: ${textColor}; font-size: 12px; text-align: center; outline: none; } .mr-setting-row input[type="number"]:focus { border-color: #0078d4; } .mr-setting-row input[type="checkbox"] { accent-color: #0078d4; width: 16px; height: 16px; } .mr-setting-section { margin-top: 8px; } .mr-setting-section .section-title { font-size: 11px; font-weight: 600; opacity: 0.5; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 4px; } .mr-divider { height: 1px; background: ${borderColor}; margin: 8px 0; } .mr-auth-box { padding: 8px 10px; margin: 4px 0; background: rgba(255,180,0,0.1); border: 1px solid rgba(255,180,0,0.4); border-radius: 8px; } .mr-auth-box .mr-setting-row { padding: 4px 0; } .mr-auth-box label { font-size: 12px; font-weight: 500; } .mr-auth-box .hint { font-size: 10px; opacity: 0.65; margin-top: 2px; padding-left: 2px; } .mr-collapsible { margin: 6px 0; background: ${cardBg}; border-radius: 8px; overflow: hidden; border: 1px solid ${borderColor}; } .mr-collapsible-header { display: flex; align-items: center; justify-content: space-between; padding: 8px 10px; cursor: pointer; font-size: 12px; font-weight: 500; transition: background 0.2s; } .mr-collapsible-header:hover { background: ${cardHover}; } .mr-collapsible-header .arrow { font-size: 10px; opacity: 0.6; transition: transform 0.25s ease; } .mr-collapsible.open .arrow { transform: rotate(180deg); } .mr-collapsible-body { max-height: 0; overflow: hidden; transition: max-height 0.3s ease; } .mr-collapsible.open .mr-collapsible-body { max-height: 260px; overflow-y: auto; } .mr-collapsible-body::-webkit-scrollbar { width: 4px; } .mr-collapsible-body::-webkit-scrollbar-thumb { background: rgba(128,128,128,0.3); border-radius: 2px; } .mr-collapsible-body-inner { padding: 4px 8px 8px 8px; } .mr-task-order-item { display: flex; align-items: center; gap: 6px; padding: 5px 8px; margin: 2px 0; background: rgba(255,255,255,0.7); border-radius: 6px; font-size: 12px; cursor: grab; transition: background 0.15s; } .mr-task-order-item:hover { background: ${cardHover}; } .mr-task-order-item.dragging { opacity: 0.4; } .mr-task-order-item.completed { opacity: 0.55; } .mr-task-order-item .drag-handle { opacity: 0.4; cursor: grab; font-size: 14px; } .mr-task-order-item .task-name { flex: 1; } .mr-task-order-item .toggle { font-size: 14px; cursor: pointer; } .mr-task-order-item .lock-badge { font-size: 10px; padding: 1px 5px; border-radius: 4px; background: rgba(255,180,0,0.25); color: #b37700; margin-left: 4px; display: none; } .mr-task-order-item.locked .lock-badge { display: inline-block; } .mr-task-order-item .done-badge { font-size: 10px; padding: 1px 5px; border-radius: 4px; background: rgba(40,167,69,0.2); color: #1e7e34; display: none; } .mr-task-order-item.completed .done-badge { display: inline-block; } .mr-random-row { padding: 8px 10px; margin: 4px 0; background: rgba(0,120,212,0.08); border: 1px dashed rgba(0,120,212,0.3); border-radius: 6px; } .mr-random-row .mr-setting-row { padding: 0; } .mr-random-row label { font-size: 12px; font-weight: 500; } .mr-random-row .hint { font-size: 10px; opacity: 0.55; margin-top: 2px; } #mr-settings-panel { display: none; margin-top: 8px; } #mr-settings-panel.visible { display: block; } .mr-save-bar { position: sticky; bottom: -12px; margin: 12px -14px -12px -14px; padding: 10px 14px; background: ${bg}; backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px); border-top: 1px solid ${borderColor}; z-index: 2; } `); const container = document.createElement('div'); container.id = 'mr-float-container'; container.innerHTML = `