Patriots.win Auto Upvoter for DepartmentOfOffense

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})();