Automatically complete Microsoft Rewards tasks with one click
Size
10.6 KB
Version
1.0.1
Created
Jan 11, 2026
Updated
24 days ago
1// ==UserScript==
2// @name Microsoft Rewards Auto Task Completer
3// @description Automatically complete Microsoft Rewards tasks with one click
4// @version 1.0.1
5// @match https://*.rewards.bing.com/*
6// @icon https://rewards.bing.com/rewards.png
7// @grant GM.getValue
8// @grant GM.setValue
9// @grant GM.openInTab
10// ==/UserScript==
11(function() {
12 'use strict';
13
14 console.log('Microsoft Rewards Auto Task Completer - Extension loaded');
15
16 // Debounce function to prevent excessive calls
17 function debounce(func, wait) {
18 let timeout;
19 return function executedFunction(...args) {
20 const later = () => {
21 clearTimeout(timeout);
22 func(...args);
23 };
24 clearTimeout(timeout);
25 timeout = setTimeout(later, wait);
26 };
27 }
28
29 // Main initialization function
30 async function init() {
31 console.log('Initializing Microsoft Rewards Auto Task Completer');
32
33 // Wait for the page to fully load
34 if (document.readyState === 'loading') {
35 document.addEventListener('DOMContentLoaded', init);
36 return;
37 }
38
39 // Wait a bit for Angular to render the content
40 await new Promise(resolve => setTimeout(resolve, 3000));
41
42 createAutoCompleteButton();
43 observePageChanges();
44 }
45
46 // Create the auto-complete button
47 function createAutoCompleteButton() {
48 console.log('Creating auto-complete button');
49
50 // Check if button already exists
51 if (document.getElementById('rewards-auto-complete-btn')) {
52 console.log('Button already exists');
53 return;
54 }
55
56 // Find the rewards banner to place our button
57 const banner = document.querySelector('#rewardsBanner .banner-padded-content');
58 if (!banner) {
59 console.log('Banner not found, retrying in 2 seconds');
60 setTimeout(createAutoCompleteButton, 2000);
61 return;
62 }
63
64 // Create button container
65 const buttonContainer = document.createElement('div');
66 buttonContainer.id = 'rewards-auto-complete-container';
67 buttonContainer.style.cssText = `
68 position: fixed;
69 bottom: 30px;
70 right: 30px;
71 z-index: 10000;
72 display: flex;
73 flex-direction: column;
74 gap: 10px;
75 `;
76
77 // Create main button
78 const button = document.createElement('button');
79 button.id = 'rewards-auto-complete-btn';
80 button.innerHTML = '🚀 Auto Complete Tasks';
81 button.style.cssText = `
82 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
83 color: white;
84 border: none;
85 padding: 15px 25px;
86 font-size: 16px;
87 font-weight: bold;
88 border-radius: 30px;
89 cursor: pointer;
90 box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4);
91 transition: all 0.3s ease;
92 font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
93 `;
94
95 // Add hover effect
96 button.addEventListener('mouseenter', () => {
97 button.style.transform = 'translateY(-2px)';
98 button.style.boxShadow = '0 6px 20px rgba(102, 126, 234, 0.6)';
99 });
100
101 button.addEventListener('mouseleave', () => {
102 button.style.transform = 'translateY(0)';
103 button.style.boxShadow = '0 4px 15px rgba(102, 126, 234, 0.4)';
104 });
105
106 // Add click handler
107 button.addEventListener('click', startAutoComplete);
108
109 // Create status display
110 const statusDiv = document.createElement('div');
111 statusDiv.id = 'rewards-auto-complete-status';
112 statusDiv.style.cssText = `
113 background: white;
114 color: #333;
115 padding: 12px 20px;
116 border-radius: 20px;
117 font-size: 14px;
118 font-weight: 500;
119 box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
120 display: none;
121 text-align: center;
122 font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
123 `;
124
125 buttonContainer.appendChild(button);
126 buttonContainer.appendChild(statusDiv);
127 document.body.appendChild(buttonContainer);
128
129 console.log('Auto-complete button created successfully');
130 }
131
132 // Start the auto-complete process
133 async function startAutoComplete() {
134 console.log('Starting auto-complete process');
135
136 const button = document.getElementById('rewards-auto-complete-btn');
137 const statusDiv = document.getElementById('rewards-auto-complete-status');
138
139 // Disable button during processing
140 button.disabled = true;
141 button.style.opacity = '0.6';
142 button.style.cursor = 'not-allowed';
143
144 // Show status
145 statusDiv.style.display = 'block';
146 statusDiv.textContent = '🔍 Scanning for tasks...';
147
148 try {
149 // Get all task cards
150 const tasks = await getAllTasks();
151 console.log(`Found ${tasks.length} total tasks`);
152
153 // Filter incomplete tasks with valid links
154 const incompleteTasks = tasks.filter(task => !task.isCompleted && task.hasLink && task.href && !task.href.startsWith('microsoft-edge://'));
155 console.log(`Found ${incompleteTasks.length} incomplete tasks to complete`);
156
157 if (incompleteTasks.length === 0) {
158 statusDiv.textContent = '✅ All tasks completed!';
159 setTimeout(() => {
160 statusDiv.style.display = 'none';
161 }, 3000);
162 button.disabled = false;
163 button.style.opacity = '1';
164 button.style.cursor = 'pointer';
165 return;
166 }
167
168 // Save current state
169 await GM.setValue('autoCompleteInProgress', true);
170 await GM.setValue('tasksToComplete', JSON.stringify(incompleteTasks));
171 await GM.setValue('currentTaskIndex', 0);
172
173 statusDiv.textContent = `🎯 Opening task 1 of ${incompleteTasks.length}...`;
174
175 // Start completing tasks
176 await completeTasksSequentially(incompleteTasks);
177
178 } catch (error) {
179 console.error('Error during auto-complete:', error);
180 statusDiv.textContent = '❌ Error occurred';
181 statusDiv.style.background = '#ff4444';
182 statusDiv.style.color = 'white';
183 setTimeout(() => {
184 statusDiv.style.display = 'none';
185 statusDiv.style.background = 'white';
186 statusDiv.style.color = '#333';
187 }, 3000);
188 button.disabled = false;
189 button.style.opacity = '1';
190 button.style.cursor = 'pointer';
191 }
192 }
193
194 // Get all tasks from the page
195 async function getAllTasks() {
196 const cards = Array.from(document.querySelectorAll('mee-card[role="listitem"]'));
197
198 return cards.map((card, index) => {
199 const link = card.querySelector('a.ds-card-sec, a[href*="bing.com"]');
200 const title = card.querySelector('.c-heading, h3')?.textContent?.trim();
201 const description = card.querySelector('.c-paragraph-4, p')?.textContent?.trim();
202 const href = link?.getAttribute('href');
203 const isCompleted = card.querySelector('.mee-icon-SkypeCircleCheck, [data-bi-id*="complete"]') !== null;
204
205 return {
206 index,
207 title,
208 description,
209 href,
210 isCompleted,
211 hasLink: !!href
212 };
213 });
214 }
215
216 // Complete tasks sequentially
217 async function completeTasksSequentially(tasks) {
218 const statusDiv = document.getElementById('rewards-auto-complete-status');
219
220 for (let i = 0; i < tasks.length; i++) {
221 const task = tasks[i];
222 console.log(`Opening task ${i + 1}/${tasks.length}: ${task.title}`);
223
224 statusDiv.textContent = `🎯 Task ${i + 1}/${tasks.length}: ${task.title || 'Opening...'}`;
225
226 // Save progress
227 await GM.setValue('currentTaskIndex', i);
228
229 // Open task in new tab
230 await GM.openInTab(task.href, false);
231
232 // Wait before opening next task
233 await new Promise(resolve => setTimeout(resolve, 5000));
234 }
235
236 // All tasks completed
237 console.log('All tasks opened successfully');
238 statusDiv.textContent = '✅ All tasks opened! Check tabs to complete them.';
239 statusDiv.style.background = '#4CAF50';
240 statusDiv.style.color = 'white';
241
242 // Clean up state
243 await GM.deleteValue('autoCompleteInProgress');
244 await GM.deleteValue('tasksToComplete');
245 await GM.deleteValue('currentTaskIndex');
246
247 // Re-enable button
248 setTimeout(() => {
249 const button = document.getElementById('rewards-auto-complete-btn');
250 button.disabled = false;
251 button.style.opacity = '1';
252 button.style.cursor = 'pointer';
253 statusDiv.style.display = 'none';
254 statusDiv.style.background = 'white';
255 statusDiv.style.color = '#333';
256 }, 5000);
257 }
258
259 // Observe page changes to re-inject button if needed
260 function observePageChanges() {
261 const debouncedCheck = debounce(() => {
262 if (!document.getElementById('rewards-auto-complete-btn')) {
263 console.log('Button removed, recreating...');
264 createAutoCompleteButton();
265 }
266 }, 1000);
267
268 const observer = new MutationObserver(debouncedCheck);
269 observer.observe(document.body, {
270 childList: true,
271 subtree: true
272 });
273
274 console.log('Page observer initialized');
275 }
276
277 // Check if we're resuming from a task completion
278 async function checkResumeState() {
279 const inProgress = await GM.getValue('autoCompleteInProgress', false);
280
281 if (inProgress) {
282 console.log('Detected incomplete auto-complete session');
283 const tasksJson = await GM.getValue('tasksToComplete', '[]');
284 const currentIndex = await GM.getValue('currentTaskIndex', 0);
285
286 try {
287 const tasks = JSON.parse(tasksJson);
288 if (currentIndex < tasks.length - 1) {
289 console.log(`Resuming from task ${currentIndex + 1}`);
290 // Could implement resume logic here if needed
291 }
292 } catch (e) {
293 console.error('Error parsing saved tasks:', e);
294 }
295 }
296 }
297
298 // Start the extension
299 checkResumeState();
300 init();
301
302})();