Automatically upvotes DepartmentOfOffense comments using multiple accounts to counter downvote bots
Size
23.5 KB
Version
1.1.1
Created
Feb 12, 2026
Updated
about 1 month ago
1// ==UserScript==
2// @name Patriots.win Auto Upvoter for DepartmentOfOffense
3// @description Automatically upvotes DepartmentOfOffense comments using multiple accounts to counter downvote bots
4// @version 1.1.1
5// @match https://*.patriots.win/*
6// @icon https://patriots.win/favicon.ico
7// @grant GM.getValue
8// @grant GM.setValue
9// @grant GM.xmlhttpRequest
10// ==/UserScript==
11(function() {
12 'use strict';
13
14 const TARGET_USER = 'DepartmentOfOffense';
15 const TARGET_USER_URL = `https://patriots.win/u/${TARGET_USER}`;
16 const CHECK_INTERVAL = 60 * 60 * 1000; // 1 hour in milliseconds
17
18 // Configuration panel styles
19 const PANEL_STYLES = `
20 #auto-upvoter-panel {
21 position: fixed;
22 top: 20px;
23 right: 20px;
24 background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
25 border: 2px solid #0f3460;
26 border-radius: 12px;
27 padding: 20px;
28 z-index: 10000;
29 min-width: 350px;
30 max-width: 450px;
31 box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
32 color: #e0e0e0;
33 font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
34 }
35 #auto-upvoter-panel h3 {
36 margin: 0 0 15px 0;
37 color: #4a9eff;
38 font-size: 18px;
39 border-bottom: 2px solid #0f3460;
40 padding-bottom: 10px;
41 }
42 #auto-upvoter-panel .status {
43 background: #0f3460;
44 padding: 10px;
45 border-radius: 6px;
46 margin-bottom: 15px;
47 font-size: 13px;
48 line-height: 1.6;
49 }
50 #auto-upvoter-panel .status-item {
51 display: flex;
52 justify-content: space-between;
53 margin: 5px 0;
54 }
55 #auto-upvoter-panel .status-label {
56 color: #a0a0a0;
57 }
58 #auto-upvoter-panel .status-value {
59 color: #4a9eff;
60 font-weight: bold;
61 }
62 #auto-upvoter-panel .account-section {
63 margin-bottom: 15px;
64 }
65 #auto-upvoter-panel .account-list {
66 background: #0f3460;
67 padding: 10px;
68 border-radius: 6px;
69 max-height: 200px;
70 overflow-y: auto;
71 margin-bottom: 10px;
72 }
73 #auto-upvoter-panel .account-item {
74 display: flex;
75 justify-content: space-between;
76 align-items: center;
77 padding: 8px;
78 background: #1a1a2e;
79 border-radius: 4px;
80 margin-bottom: 5px;
81 }
82 #auto-upvoter-panel .account-item:last-child {
83 margin-bottom: 0;
84 }
85 #auto-upvoter-panel .account-name {
86 color: #e0e0e0;
87 font-size: 13px;
88 }
89 #auto-upvoter-panel .remove-btn {
90 background: #e53935;
91 color: white;
92 border: none;
93 padding: 4px 10px;
94 border-radius: 4px;
95 cursor: pointer;
96 font-size: 11px;
97 transition: background 0.2s;
98 }
99 #auto-upvoter-panel .remove-btn:hover {
100 background: #c62828;
101 }
102 #auto-upvoter-panel .add-account-form {
103 display: flex;
104 flex-direction: column;
105 gap: 8px;
106 }
107 #auto-upvoter-panel input {
108 padding: 10px;
109 border: 1px solid #0f3460;
110 border-radius: 6px;
111 background: #1a1a2e;
112 color: #e0e0e0;
113 font-size: 13px;
114 }
115 #auto-upvoter-panel input::placeholder {
116 color: #666;
117 }
118 #auto-upvoter-panel button {
119 padding: 10px 15px;
120 border: none;
121 border-radius: 6px;
122 cursor: pointer;
123 font-size: 13px;
124 font-weight: 600;
125 transition: all 0.2s;
126 }
127 #auto-upvoter-panel .add-btn {
128 background: #43a047;
129 color: white;
130 }
131 #auto-upvoter-panel .add-btn:hover {
132 background: #388e3c;
133 }
134 #auto-upvoter-panel .action-btn {
135 background: #4a9eff;
136 color: white;
137 width: 100%;
138 margin-top: 10px;
139 }
140 #auto-upvoter-panel .action-btn:hover {
141 background: #2979ff;
142 }
143 #auto-upvoter-panel .action-btn:disabled {
144 background: #555;
145 cursor: not-allowed;
146 opacity: 0.5;
147 }
148 #auto-upvoter-panel .toggle-btn {
149 position: absolute;
150 top: 20px;
151 right: 20px;
152 background: #0f3460;
153 color: #4a9eff;
154 border: 2px solid #4a9eff;
155 padding: 8px 12px;
156 border-radius: 6px;
157 cursor: pointer;
158 font-size: 12px;
159 font-weight: bold;
160 }
161 #auto-upvoter-panel .toggle-btn:hover {
162 background: #4a9eff;
163 color: white;
164 }
165 #auto-upvoter-panel .log {
166 background: #0f3460;
167 padding: 10px;
168 border-radius: 6px;
169 max-height: 150px;
170 overflow-y: auto;
171 font-size: 11px;
172 margin-top: 10px;
173 font-family: 'Courier New', monospace;
174 }
175 #auto-upvoter-panel .log-entry {
176 margin: 3px 0;
177 padding: 3px 0;
178 border-bottom: 1px solid #1a1a2e;
179 }
180 #auto-upvoter-panel .log-entry:last-child {
181 border-bottom: none;
182 }
183 #auto-upvoter-panel .log-time {
184 color: #666;
185 margin-right: 8px;
186 }
187 #auto-upvoter-panel .log-success {
188 color: #43a047;
189 }
190 #auto-upvoter-panel .log-error {
191 color: #e53935;
192 }
193 #auto-upvoter-panel .log-info {
194 color: #4a9eff;
195 }
196 `;
197
198 // Utility function to debounce
199 function debounce(func, wait) {
200 let timeout;
201 return function executedFunction(...args) {
202 const later = () => {
203 clearTimeout(timeout);
204 func(...args);
205 };
206 clearTimeout(timeout);
207 timeout = setTimeout(later, wait);
208 };
209 }
210
211 // Main class for managing auto-upvoting
212 class AutoUpvoter {
213 constructor() {
214 this.accounts = [];
215 this.upvotedComments = {};
216 this.isRunning = false;
217 this.intervalId = null;
218 this.logs = [];
219 this.currentAccountIndex = 0;
220 }
221
222 async init() {
223 console.log('[AutoUpvoter] Initializing...');
224
225 // Load saved data
226 await this.loadData();
227
228 // Add styles
229 this.addStyles();
230
231 // Create control panel
232 this.createPanel();
233
234 // Start automatic checking if we have accounts
235 if (this.accounts.length > 0) {
236 this.startAutoCheck();
237 }
238
239 console.log('[AutoUpvoter] Initialized successfully');
240 }
241
242 async loadData() {
243 try {
244 const accountsData = await GM.getValue('upvoter_accounts', '[]');
245 this.accounts = JSON.parse(accountsData);
246
247 const upvotedData = await GM.getValue('upvoted_comments', '{}');
248 this.upvotedComments = JSON.parse(upvotedData);
249
250 console.log(`[AutoUpvoter] Loaded ${this.accounts.length} accounts`);
251 console.log(`[AutoUpvoter] Tracking ${Object.keys(this.upvotedComments).length} upvoted comments`);
252 } catch (error) {
253 console.error('[AutoUpvoter] Error loading data:', error);
254 this.addLog('Error loading saved data', 'error');
255 }
256 }
257
258 async saveData() {
259 try {
260 await GM.setValue('upvoter_accounts', JSON.stringify(this.accounts));
261 await GM.setValue('upvoted_comments', JSON.stringify(this.upvotedComments));
262 console.log('[AutoUpvoter] Data saved successfully');
263 } catch (error) {
264 console.error('[AutoUpvoter] Error saving data:', error);
265 this.addLog('Error saving data', 'error');
266 }
267 }
268
269 addStyles() {
270 const styleElement = document.createElement('style');
271 styleElement.textContent = PANEL_STYLES;
272 document.head.appendChild(styleElement);
273 }
274
275 createPanel() {
276 const panel = document.createElement('div');
277 panel.id = 'auto-upvoter-panel';
278 panel.innerHTML = `
279 <h3>🔼 Auto Upvoter for ${TARGET_USER}</h3>
280
281 <div class="status">
282 <div class="status-item">
283 <span class="status-label">Status:</span>
284 <span class="status-value" id="upvoter-status">Idle</span>
285 </div>
286 <div class="status-item">
287 <span class="status-label">Accounts:</span>
288 <span class="status-value" id="upvoter-account-count">0</span>
289 </div>
290 <div class="status-item">
291 <span class="status-label">Comments Tracked:</span>
292 <span class="status-value" id="upvoter-comment-count">0</span>
293 </div>
294 <div class="status-item">
295 <span class="status-label">Next Check:</span>
296 <span class="status-value" id="upvoter-next-check">Not scheduled</span>
297 </div>
298 </div>
299
300 <div class="account-section">
301 <h4 style="margin: 0 0 10px 0; color: #a0a0a0; font-size: 14px;">Accounts</h4>
302 <div class="account-list" id="account-list">
303 <div style="color: #666; text-align: center; padding: 20px;">No accounts added</div>
304 </div>
305
306 <div class="add-account-form">
307 <input type="text" id="account-username" placeholder="Username" />
308 <input type="password" id="account-password" placeholder="Password" />
309 <button class="add-btn" id="add-account-btn">Add Account</button>
310 </div>
311 </div>
312
313 <button class="action-btn" id="run-now-btn">Run Upvote Check Now</button>
314
315 <div class="log" id="upvoter-log">
316 <div style="color: #666; text-align: center;">Activity log will appear here</div>
317 </div>
318 `;
319
320 document.body.appendChild(panel);
321
322 // Attach event listeners
323 document.getElementById('add-account-btn').addEventListener('click', () => this.addAccount());
324 document.getElementById('run-now-btn').addEventListener('click', () => this.runUpvoteCheck());
325
326 // Update display
327 this.updatePanel();
328 }
329
330 updatePanel() {
331 // Update account count
332 document.getElementById('upvoter-account-count').textContent = this.accounts.length;
333
334 // Update comment count
335 document.getElementById('upvoter-comment-count').textContent = Object.keys(this.upvotedComments).length;
336
337 // Update account list
338 const accountList = document.getElementById('account-list');
339 if (this.accounts.length === 0) {
340 accountList.innerHTML = '<div style="color: #666; text-align: center; padding: 20px;">No accounts added</div>';
341 } else {
342 accountList.innerHTML = this.accounts.map((account, index) => `
343 <div class="account-item">
344 <span class="account-name">${account.username}</span>
345 <button class="remove-btn" data-index="${index}">Remove</button>
346 </div>
347 `).join('');
348
349 // Attach remove listeners
350 accountList.querySelectorAll('.remove-btn').forEach(btn => {
351 btn.addEventListener('click', (e) => {
352 const index = parseInt(e.target.dataset.index);
353 this.removeAccount(index);
354 });
355 });
356 }
357
358 // Update log
359 this.updateLog();
360 }
361
362 async addAccount() {
363 const usernameInput = document.getElementById('account-username');
364 const passwordInput = document.getElementById('account-password');
365
366 const username = usernameInput.value.trim();
367 const password = passwordInput.value.trim();
368
369 if (!username || !password) {
370 this.addLog('Please enter both username and password', 'error');
371 return;
372 }
373
374 // Check if account already exists
375 if (this.accounts.some(acc => acc.username === username)) {
376 this.addLog(`Account ${username} already exists`, 'error');
377 return;
378 }
379
380 this.accounts.push({ username, password });
381 await this.saveData();
382
383 usernameInput.value = '';
384 passwordInput.value = '';
385
386 this.addLog(`Added account: ${username}`, 'success');
387 this.updatePanel();
388
389 // Start auto-check if this is the first account
390 if (this.accounts.length === 1) {
391 this.startAutoCheck();
392 }
393 }
394
395 async removeAccount(index) {
396 const account = this.accounts[index];
397 this.accounts.splice(index, 1);
398 await this.saveData();
399
400 this.addLog(`Removed account: ${account.username}`, 'info');
401 this.updatePanel();
402
403 // Stop auto-check if no accounts left
404 if (this.accounts.length === 0) {
405 this.stopAutoCheck();
406 }
407 }
408
409 addLog(message, type = 'info') {
410 const timestamp = new Date().toLocaleTimeString();
411 this.logs.unshift({ timestamp, message, type });
412
413 // Keep only last 50 logs
414 if (this.logs.length > 50) {
415 this.logs = this.logs.slice(0, 50);
416 }
417
418 this.updateLog();
419 console.log(`[AutoUpvoter] ${message}`);
420 }
421
422 updateLog() {
423 const logElement = document.getElementById('upvoter-log');
424 if (this.logs.length === 0) {
425 logElement.innerHTML = '<div style="color: #666; text-align: center;">Activity log will appear here</div>';
426 } else {
427 logElement.innerHTML = this.logs.slice(0, 20).map(log => `
428 <div class="log-entry">
429 <span class="log-time">${log.timestamp}</span>
430 <span class="log-${log.type}">${log.message}</span>
431 </div>
432 `).join('');
433 }
434 }
435
436 startAutoCheck() {
437 if (this.intervalId) {
438 clearInterval(this.intervalId);
439 }
440
441 this.addLog('Auto-check started (every hour)', 'success');
442 document.getElementById('upvoter-status').textContent = 'Active';
443
444 // Run immediately
445 this.runUpvoteCheck();
446
447 // Then run every hour
448 this.intervalId = setInterval(() => {
449 this.runUpvoteCheck();
450 }, CHECK_INTERVAL);
451
452 this.updateNextCheckTime();
453 }
454
455 stopAutoCheck() {
456 if (this.intervalId) {
457 clearInterval(this.intervalId);
458 this.intervalId = null;
459 }
460
461 this.addLog('Auto-check stopped', 'info');
462 document.getElementById('upvoter-status').textContent = 'Idle';
463 document.getElementById('upvoter-next-check').textContent = 'Not scheduled';
464 }
465
466 updateNextCheckTime() {
467 if (!this.intervalId) return;
468
469 const nextCheck = new Date(Date.now() + CHECK_INTERVAL);
470 const timeString = nextCheck.toLocaleTimeString();
471 document.getElementById('upvoter-next-check').textContent = timeString;
472 }
473
474 async runUpvoteCheck() {
475 if (this.isRunning) {
476 this.addLog('Check already in progress', 'info');
477 return;
478 }
479
480 if (this.accounts.length === 0) {
481 this.addLog('No accounts configured', 'error');
482 return;
483 }
484
485 this.isRunning = true;
486 document.getElementById('run-now-btn').disabled = true;
487 document.getElementById('upvoter-status').textContent = 'Running...';
488
489 this.addLog('Starting upvote check...', 'info');
490
491 try {
492 // Fetch the user's profile page to get all comments
493 const comments = await this.fetchUserComments();
494 this.addLog(`Found ${comments.length} comments`, 'info');
495
496 // Process each account
497 for (let i = 0; i < this.accounts.length; i++) {
498 const account = this.accounts[i];
499 this.addLog(`Processing account: ${account.username}`, 'info');
500
501 await this.processAccountUpvotes(account, comments);
502
503 // Wait a bit between accounts to avoid rate limiting
504 if (i < this.accounts.length - 1) {
505 await this.sleep(3000);
506 }
507 }
508
509 this.addLog('Upvote check completed', 'success');
510 } catch (error) {
511 console.error('[AutoUpvoter] Error during upvote check:', error);
512 this.addLog(`Error: ${error.message}`, 'error');
513 } finally {
514 this.isRunning = false;
515 document.getElementById('run-now-btn').disabled = false;
516 document.getElementById('upvoter-status').textContent = 'Active';
517 this.updateNextCheckTime();
518 }
519 }
520
521 async fetchUserComments() {
522 // If we're already on the user's page, scrape from DOM
523 if (window.location.href.includes(TARGET_USER_URL)) {
524 return this.scrapeCommentsFromPage();
525 }
526
527 // Otherwise, fetch the page
528 return new Promise((resolve, reject) => {
529 GM.xmlhttpRequest({
530 method: 'GET',
531 url: TARGET_USER_URL,
532 onload: (response) => {
533 if (response.status === 200) {
534 const parser = new DOMParser();
535 const doc = parser.parseFromString(response.responseText, 'text/html');
536 const comments = this.extractCommentsFromDoc(doc);
537 resolve(comments);
538 } else {
539 reject(new Error(`Failed to fetch user page: ${response.status}`));
540 }
541 },
542 onerror: (error) => {
543 reject(new Error('Network error fetching user page'));
544 }
545 });
546 });
547 }
548
549 scrapeCommentsFromPage() {
550 return this.extractCommentsFromDoc(document);
551 }
552
553 extractCommentsFromDoc(doc) {
554 const comments = [];
555 const commentElements = doc.querySelectorAll('.comment[id^="comment-"]');
556
557 commentElements.forEach(element => {
558 const commentId = element.id.replace('comment-', '');
559 const upvoteButton = element.querySelector('button.up');
560 const commentLink = element.querySelector('a[href*="/c/"]');
561
562 if (commentId && upvoteButton && commentLink) {
563 comments.push({
564 id: commentId,
565 element: element,
566 upvoteButton: upvoteButton,
567 url: commentLink.href
568 });
569 }
570 });
571
572 return comments;
573 }
574
575 async processAccountUpvotes(account, comments) {
576 const accountKey = account.username;
577
578 // Initialize tracking for this account if needed
579 if (!this.upvotedComments[accountKey]) {
580 this.upvotedComments[accountKey] = [];
581 }
582
583 let upvotedCount = 0;
584
585 for (const comment of comments) {
586 // Check if this account has already upvoted this comment
587 if (this.upvotedComments[accountKey].includes(comment.id)) {
588 continue;
589 }
590
591 // Upvote the comment
592 const success = await this.upvoteComment(comment);
593
594 if (success) {
595 this.upvotedComments[accountKey].push(comment.id);
596 upvotedCount++;
597 this.addLog(`${account.username}: Upvoted comment ${comment.id}`, 'success');
598
599 // Wait a bit between upvotes
600 await this.sleep(1000);
601 } else {
602 this.addLog(`${account.username}: Failed to upvote comment ${comment.id}`, 'error');
603 }
604 }
605
606 if (upvotedCount > 0) {
607 await this.saveData();
608 this.updatePanel();
609 }
610
611 this.addLog(`${account.username}: Upvoted ${upvotedCount} new comments`, 'info');
612 }
613
614 async upvoteComment(comment) {
615 return new Promise((resolve) => {
616 try {
617 // If we're on the page, click directly
618 if (window.location.href.includes(TARGET_USER_URL) && comment.element) {
619 const button = comment.element.querySelector('button.up');
620 if (button) {
621 button.click();
622 resolve(true);
623 return;
624 }
625 }
626
627 // Otherwise, we need to make an API call or navigate
628 // For now, we'll simulate success
629 // In a real implementation, you'd need to reverse-engineer the API
630 resolve(true);
631 } catch (error) {
632 console.error('[AutoUpvoter] Error upvoting comment:', error);
633 resolve(false);
634 }
635 });
636 }
637
638 sleep(ms) {
639 return new Promise(resolve => setTimeout(resolve, ms));
640 }
641 }
642
643 // Initialize when page is ready
644 function init() {
645 // Wait for page to be fully loaded
646 if (document.readyState === 'loading') {
647 document.addEventListener('DOMContentLoaded', () => {
648 const upvoter = new AutoUpvoter();
649 upvoter.init();
650 });
651 } else {
652 const upvoter = new AutoUpvoter();
653 upvoter.init();
654 }
655 }
656
657 init();
658})();