Advanced Bumble automation with smart filtering, auto-messaging, and activity tracking
Size
43.0 KB
Version
1.0.1
Created
Feb 5, 2026
Updated
about 1 month ago
1// ==UserScript==
2// @name Bumble Auto Swiper Pro
3// @description Advanced Bumble automation with smart filtering, auto-messaging, and activity tracking
4// @version 1.0.1
5// @match https://*.bumble.com/*
6// @icon https://robomonkey.io/favicon.ico
7// @grant GM.getValue
8// @grant GM.setValue
9// @grant GM.deleteValue
10// @grant GM.listValues
11// ==/UserScript==
12(function() {
13 'use strict';
14
15 // ============================================
16 // CONFIGURATION & STATE MANAGEMENT
17 // ============================================
18
19 const CONFIG = {
20 MIN_DELAY: 2000,
21 MAX_DELAY: 5000,
22 PAUSE_AFTER_SWIPES: 20,
23 PAUSE_DURATION: 15000,
24 PROFILE_LOAD_TIMEOUT: 5000,
25 DEFAULT_FILTERED_WORDS: ['trans', 'transgender', 'trans woman', 'trans-woman', 'transwoman'],
26 STORAGE_KEYS: {
27 IS_ACTIVE: 'bumble_auto_swipe_active',
28 SETTINGS: 'bumble_settings',
29 SWIPE_HISTORY: 'bumble_swipe_history',
30 FILTERED_WORDS: 'bumble_filtered_words',
31 AUTO_MESSAGE_TEMPLATES: 'bumble_message_templates',
32 STATS: 'bumble_stats'
33 }
34 };
35
36 let state = {
37 isActive: false,
38 isPaused: false,
39 swipeCount: 0,
40 stats: {
41 totalSwipes: 0,
42 rightSwipes: 0,
43 leftSwipes: 0,
44 matches: 0,
45 messagesSent: 0
46 },
47 currentProfile: null,
48 observer: null
49 };
50
51 // ============================================
52 // UTILITY FUNCTIONS
53 // ============================================
54
55 function debounce(func, wait) {
56 let timeout;
57 return function executedFunction(...args) {
58 const later = () => {
59 clearTimeout(timeout);
60 func(...args);
61 };
62 clearTimeout(timeout);
63 timeout = setTimeout(later, wait);
64 };
65 }
66
67 function randomDelay(min = CONFIG.MIN_DELAY, max = CONFIG.MAX_DELAY) {
68 return Math.floor(Math.random() * (max - min + 1)) + min;
69 }
70
71 async function wait(ms) {
72 return new Promise(resolve => setTimeout(resolve, ms));
73 }
74
75 function log(message, data = null) {
76 const timestamp = new Date().toISOString();
77 console.log(`[Bumble Auto Swiper] ${timestamp} - ${message}`, data || '');
78 }
79
80 function logError(message, error = null) {
81 const timestamp = new Date().toISOString();
82 console.error(`[Bumble Auto Swiper ERROR] ${timestamp} - ${message}`, error || '');
83 }
84
85 // ============================================
86 // STORAGE FUNCTIONS
87 // ============================================
88
89 async function loadSettings() {
90 try {
91 const settings = await GM.getValue(CONFIG.STORAGE_KEYS.SETTINGS, {
92 minDelay: 2000,
93 maxDelay: 5000,
94 pauseAfterSwipes: 20,
95 autoMessageEnabled: false,
96 verificationPriority: true
97 });
98 return settings;
99 } catch (error) {
100 logError('Failed to load settings', error);
101 return {};
102 }
103 }
104
105 async function saveSettings(settings) {
106 try {
107 await GM.setValue(CONFIG.STORAGE_KEYS.SETTINGS, settings);
108 log('Settings saved successfully');
109 } catch (error) {
110 logError('Failed to save settings', error);
111 }
112 }
113
114 async function loadFilteredWords() {
115 try {
116 const words = await GM.getValue(CONFIG.STORAGE_KEYS.FILTERED_WORDS, CONFIG.DEFAULT_FILTERED_WORDS);
117 return words;
118 } catch (error) {
119 logError('Failed to load filtered words', error);
120 return CONFIG.DEFAULT_FILTERED_WORDS;
121 }
122 }
123
124 async function saveFilteredWords(words) {
125 try {
126 await GM.setValue(CONFIG.STORAGE_KEYS.FILTERED_WORDS, words);
127 log('Filtered words saved successfully');
128 } catch (error) {
129 logError('Failed to save filtered words', error);
130 }
131 }
132
133 async function loadSwipeHistory() {
134 try {
135 const history = await GM.getValue(CONFIG.STORAGE_KEYS.SWIPE_HISTORY, { right: [], left: [] });
136 return history;
137 } catch (error) {
138 logError('Failed to load swipe history', error);
139 return { right: [], left: [] };
140 }
141 }
142
143 async function saveSwipeHistory(history) {
144 try {
145 // Keep only last 100 entries for each
146 if (history.right.length > 100) history.right = history.right.slice(-100);
147 if (history.left.length > 100) history.left = history.left.slice(-100);
148
149 await GM.setValue(CONFIG.STORAGE_KEYS.SWIPE_HISTORY, history);
150 log('Swipe history saved successfully');
151 } catch (error) {
152 logError('Failed to save swipe history', error);
153 }
154 }
155
156 async function loadStats() {
157 try {
158 const stats = await GM.getValue(CONFIG.STORAGE_KEYS.STATS, state.stats);
159 state.stats = stats;
160 return stats;
161 } catch (error) {
162 logError('Failed to load stats', error);
163 return state.stats;
164 }
165 }
166
167 async function saveStats() {
168 try {
169 await GM.setValue(CONFIG.STORAGE_KEYS.STATS, state.stats);
170 } catch (error) {
171 logError('Failed to save stats', error);
172 }
173 }
174
175 async function loadMessageTemplates() {
176 try {
177 const templates = await GM.getValue(CONFIG.STORAGE_KEYS.AUTO_MESSAGE_TEMPLATES, [
178 "Hey! How's your day going?",
179 "Hi there! I'd love to get to know you better 😊",
180 "Hey! Your profile caught my eye. What do you like to do for fun?"
181 ]);
182 return templates;
183 } catch (error) {
184 logError('Failed to load message templates', error);
185 return [];
186 }
187 }
188
189 // ============================================
190 // BUMBLE DOM INTERACTION
191 // ============================================
192
193 function findProfileCard() {
194 // Multiple selectors for Bumble's profile card structure
195 const selectors = [
196 'div[data-qa-role="profile-card"]',
197 'article[class*="encounters-story-profile"]',
198 'div[class*="encounters-album"]',
199 'div.encounters-story-profile',
200 '[class*="encounters-user"]'
201 ];
202
203 for (const selector of selectors) {
204 const element = document.querySelector(selector);
205 if (element) {
206 log('Profile card found with selector:', selector);
207 return element;
208 }
209 }
210
211 logError('Profile card not found with any selector');
212 return null;
213 }
214
215 function findSwipeButtons() {
216 // Find both like and pass buttons
217 const likeSelectors = [
218 'button[data-qa-icon="game-yes"]',
219 'button[aria-label*="Yes"]',
220 'button[class*="encounters-action--yes"]',
221 'div[class*="encounters-controls"] button:last-child',
222 'button[data-qa="pill-yes"]'
223 ];
224
225 const passSelectors = [
226 'button[data-qa-icon="game-no"]',
227 'button[aria-label*="No"]',
228 'button[class*="encounters-action--no"]',
229 'div[class*="encounters-controls"] button:first-child',
230 'button[data-qa="pill-no"]'
231 ];
232
233 let likeButton = null;
234 let passButton = null;
235
236 for (const selector of likeSelectors) {
237 likeButton = document.querySelector(selector);
238 if (likeButton) {
239 log('Like button found with selector:', selector);
240 break;
241 }
242 }
243
244 for (const selector of passSelectors) {
245 passButton = document.querySelector(selector);
246 if (passButton) {
247 log('Pass button found with selector:', selector);
248 break;
249 }
250 }
251
252 if (!likeButton || !passButton) {
253 logError('Swipe buttons not found', { likeButton: !!likeButton, passButton: !!passButton });
254 }
255
256 return { likeButton, passButton };
257 }
258
259 function extractProfileInfo() {
260 try {
261 const profileCard = findProfileCard();
262 if (!profileCard) {
263 return null;
264 }
265
266 // Extract name
267 const nameElement = profileCard.querySelector('[class*="encounters-story-profile__name"], h1, [class*="profile-name"]');
268 const name = nameElement ? nameElement.textContent.trim() : 'Unknown';
269
270 // Extract age
271 const ageElement = profileCard.querySelector('[class*="encounters-story-profile__age"], [class*="profile-age"]');
272 const age = ageElement ? ageElement.textContent.trim() : '';
273
274 // Extract bio
275 const bioElement = profileCard.querySelector('[class*="profile-bio"], [class*="encounters-story-profile__bio"], p[class*="bio"]');
276 const bio = bioElement ? bioElement.textContent.trim().toLowerCase() : '';
277
278 // Check for verification badge
279 const verificationBadge = profileCard.querySelector('[class*="verification"], [class*="verified"], svg[class*="badge"]');
280 const isVerified = !!verificationBadge;
281
282 // Extract profile image
283 const imageElement = profileCard.querySelector('img[src*="bumble"], img[class*="profile"], img[class*="photo"]');
284 const imageUrl = imageElement ? imageElement.src : '';
285
286 const profile = {
287 name,
288 age,
289 bio,
290 isVerified,
291 imageUrl,
292 timestamp: Date.now()
293 };
294
295 log('Profile extracted:', profile);
296 return profile;
297
298 } catch (error) {
299 logError('Error extracting profile info', error);
300 return null;
301 }
302 }
303
304 function shouldSwipeRight(profile, filteredWords) {
305 if (!profile) return false;
306
307 // Check for filtered words in bio
308 const bio = profile.bio.toLowerCase();
309 for (const word of filteredWords) {
310 if (bio.includes(word.toLowerCase())) {
311 log(`Filtered word detected: "${word}" in bio`);
312 return false;
313 }
314 }
315
316 // Prioritize verified profiles
317 if (profile.isVerified) {
318 log('Verified profile detected - swiping right');
319 return true;
320 }
321
322 // Default behavior - swipe right on most profiles
323 // You can add more sophisticated logic here
324 return Math.random() > 0.3; // 70% right swipe rate for non-verified
325 }
326
327 async function performSwipe(direction, profile) {
328 try {
329 const { likeButton, passButton } = findSwipeButtons();
330
331 if (!likeButton || !passButton) {
332 logError('Cannot perform swipe - buttons not found');
333 return false;
334 }
335
336 const button = direction === 'right' ? likeButton : passButton;
337
338 // Simulate human-like click
339 button.focus();
340 await wait(100);
341 button.click();
342
343 log(`Swiped ${direction} on profile:`, profile.name);
344
345 // Update stats
346 state.stats.totalSwipes++;
347 if (direction === 'right') {
348 state.stats.rightSwipes++;
349 } else {
350 state.stats.leftSwipes++;
351 }
352 await saveStats();
353
354 // Save to history
355 const history = await loadSwipeHistory();
356 history[direction].push(profile);
357 await saveSwipeHistory(history);
358
359 // Update UI
360 updateStatsDisplay();
361
362 return true;
363
364 } catch (error) {
365 logError('Error performing swipe', error);
366 return false;
367 }
368 }
369
370 // ============================================
371 // AUTO-SWIPE ENGINE
372 // ============================================
373
374 async function autoSwipeLoop() {
375 log('Auto-swipe loop started');
376
377 while (state.isActive) {
378 try {
379 // Check if paused
380 if (state.isPaused) {
381 log('Auto-swipe paused, waiting...');
382 await wait(1000);
383 continue;
384 }
385
386 // Wait for profile to load
387 await wait(CONFIG.PROFILE_LOAD_TIMEOUT);
388
389 // Extract current profile
390 const profile = extractProfileInfo();
391 if (!profile) {
392 log('No profile found, waiting for next profile...');
393 await wait(2000);
394 continue;
395 }
396
397 state.currentProfile = profile;
398
399 // Load settings and filtered words
400 const settings = await loadSettings();
401 const filteredWords = await loadFilteredWords();
402
403 // Decide swipe direction
404 const shouldRight = shouldSwipeRight(profile, filteredWords);
405 const direction = shouldRight ? 'right' : 'left';
406
407 // Perform swipe
408 const success = await performSwipe(direction, profile);
409
410 if (success) {
411 state.swipeCount++;
412
413 // Check if we need to pause
414 if (state.swipeCount >= settings.pauseAfterSwipes) {
415 log(`Reached ${state.swipeCount} swipes, taking a break...`);
416 state.isPaused = true;
417 updateToggleButton();
418
419 await wait(CONFIG.PAUSE_DURATION);
420
421 state.isPaused = false;
422 state.swipeCount = 0;
423 updateToggleButton();
424 log('Break finished, resuming auto-swipe');
425 }
426
427 // Random delay between swipes
428 const delay = randomDelay(settings.minDelay, settings.maxDelay);
429 log(`Waiting ${delay}ms before next swipe`);
430 await wait(delay);
431 } else {
432 log('Swipe failed, retrying...');
433 await wait(2000);
434 }
435
436 } catch (error) {
437 logError('Error in auto-swipe loop', error);
438 await wait(3000);
439 }
440 }
441
442 log('Auto-swipe loop stopped');
443 }
444
445 async function toggleAutoSwipe() {
446 state.isActive = !state.isActive;
447 await GM.setValue(CONFIG.STORAGE_KEYS.IS_ACTIVE, state.isActive);
448
449 if (state.isActive) {
450 log('Auto-swipe activated');
451 updateToggleButton();
452 autoSwipeLoop();
453 } else {
454 log('Auto-swipe deactivated');
455 state.swipeCount = 0;
456 state.isPaused = false;
457 updateToggleButton();
458 }
459 }
460
461 // ============================================
462 // UI CREATION
463 // ============================================
464
465 function injectStyles() {
466 const styles = `
467 /* Main Container */
468 #bumble-auto-swiper-panel {
469 position: fixed;
470 top: 20px;
471 right: 20px;
472 width: 380px;
473 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
474 border-radius: 20px;
475 box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
476 z-index: 999999;
477 font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
478 color: #ffffff;
479 overflow: hidden;
480 transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
481 }
482
483 #bumble-auto-swiper-panel.minimized {
484 width: 60px;
485 height: 60px;
486 border-radius: 50%;
487 }
488
489 #bumble-auto-swiper-panel.minimized .panel-content {
490 display: none;
491 }
492
493 /* Header */
494 .swiper-header {
495 padding: 20px;
496 background: rgba(255, 255, 255, 0.1);
497 backdrop-filter: blur(10px);
498 display: flex;
499 justify-content: space-between;
500 align-items: center;
501 border-bottom: 1px solid rgba(255, 255, 255, 0.2);
502 }
503
504 .swiper-title {
505 font-size: 18px;
506 font-weight: 700;
507 letter-spacing: 0.5px;
508 display: flex;
509 align-items: center;
510 gap: 10px;
511 }
512
513 .swiper-title::before {
514 content: '🔥';
515 font-size: 24px;
516 }
517
518 .minimize-btn {
519 background: rgba(255, 255, 255, 0.2);
520 border: none;
521 color: white;
522 width: 30px;
523 height: 30px;
524 border-radius: 50%;
525 cursor: pointer;
526 font-size: 18px;
527 display: flex;
528 align-items: center;
529 justify-content: center;
530 transition: all 0.2s;
531 }
532
533 .minimize-btn:hover {
534 background: rgba(255, 255, 255, 0.3);
535 transform: scale(1.1);
536 }
537
538 /* Toggle Button */
539 .toggle-container {
540 padding: 20px;
541 display: flex;
542 flex-direction: column;
543 gap: 15px;
544 }
545
546 .toggle-button {
547 width: 100%;
548 padding: 18px;
549 border: none;
550 border-radius: 12px;
551 font-size: 16px;
552 font-weight: 600;
553 cursor: pointer;
554 transition: all 0.3s;
555 text-transform: uppercase;
556 letter-spacing: 1px;
557 position: relative;
558 overflow: hidden;
559 }
560
561 .toggle-button::before {
562 content: '';
563 position: absolute;
564 top: 50%;
565 left: 50%;
566 width: 0;
567 height: 0;
568 border-radius: 50%;
569 background: rgba(255, 255, 255, 0.3);
570 transform: translate(-50%, -50%);
571 transition: width 0.6s, height 0.6s;
572 }
573
574 .toggle-button:hover::before {
575 width: 300px;
576 height: 300px;
577 }
578
579 .toggle-button.active {
580 background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);
581 color: white;
582 box-shadow: 0 8px 20px rgba(17, 153, 142, 0.4);
583 }
584
585 .toggle-button.inactive {
586 background: linear-gradient(135deg, #ee0979 0%, #ff6a00 100%);
587 color: white;
588 box-shadow: 0 8px 20px rgba(238, 9, 121, 0.4);
589 }
590
591 .toggle-button.paused {
592 background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
593 color: white;
594 }
595
596 /* Stats Display */
597 .stats-container {
598 padding: 0 20px 20px 20px;
599 display: grid;
600 grid-template-columns: repeat(2, 1fr);
601 gap: 12px;
602 }
603
604 .stat-box {
605 background: rgba(255, 255, 255, 0.15);
606 backdrop-filter: blur(10px);
607 padding: 15px;
608 border-radius: 12px;
609 text-align: center;
610 transition: all 0.3s;
611 }
612
613 .stat-box:hover {
614 background: rgba(255, 255, 255, 0.25);
615 transform: translateY(-2px);
616 }
617
618 .stat-value {
619 font-size: 28px;
620 font-weight: 700;
621 display: block;
622 margin-bottom: 5px;
623 }
624
625 .stat-label {
626 font-size: 12px;
627 opacity: 0.9;
628 text-transform: uppercase;
629 letter-spacing: 0.5px;
630 }
631
632 /* Tabs */
633 .tabs-container {
634 padding: 0 20px;
635 }
636
637 .tabs-header {
638 display: flex;
639 gap: 10px;
640 margin-bottom: 15px;
641 }
642
643 .tab-button {
644 flex: 1;
645 padding: 12px;
646 background: rgba(255, 255, 255, 0.1);
647 border: none;
648 border-radius: 10px;
649 color: white;
650 font-size: 14px;
651 font-weight: 600;
652 cursor: pointer;
653 transition: all 0.3s;
654 }
655
656 .tab-button:hover {
657 background: rgba(255, 255, 255, 0.2);
658 }
659
660 .tab-button.active {
661 background: rgba(255, 255, 255, 0.3);
662 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
663 }
664
665 .tab-content {
666 display: none;
667 max-height: 300px;
668 overflow-y: auto;
669 padding: 15px;
670 background: rgba(255, 255, 255, 0.1);
671 border-radius: 12px;
672 margin-bottom: 20px;
673 }
674
675 .tab-content.active {
676 display: block;
677 }
678
679 /* Settings */
680 .settings-group {
681 margin-bottom: 20px;
682 }
683
684 .settings-label {
685 display: block;
686 margin-bottom: 8px;
687 font-size: 13px;
688 font-weight: 600;
689 opacity: 0.9;
690 }
691
692 .settings-input {
693 width: 100%;
694 padding: 10px;
695 background: rgba(255, 255, 255, 0.2);
696 border: 1px solid rgba(255, 255, 255, 0.3);
697 border-radius: 8px;
698 color: white;
699 font-size: 14px;
700 }
701
702 .settings-input::placeholder {
703 color: rgba(255, 255, 255, 0.6);
704 }
705
706 .settings-input:focus {
707 outline: none;
708 background: rgba(255, 255, 255, 0.25);
709 border-color: rgba(255, 255, 255, 0.5);
710 }
711
712 .checkbox-container {
713 display: flex;
714 align-items: center;
715 gap: 10px;
716 margin-bottom: 15px;
717 }
718
719 .checkbox-container input[type="checkbox"] {
720 width: 20px;
721 height: 20px;
722 cursor: pointer;
723 }
724
725 /* History Items */
726 .history-item {
727 display: flex;
728 gap: 12px;
729 padding: 12px;
730 background: rgba(255, 255, 255, 0.1);
731 border-radius: 10px;
732 margin-bottom: 10px;
733 transition: all 0.3s;
734 }
735
736 .history-item:hover {
737 background: rgba(255, 255, 255, 0.15);
738 transform: translateX(5px);
739 }
740
741 .history-item.right {
742 border-left: 4px solid #38ef7d;
743 }
744
745 .history-item.left {
746 border-left: 4px solid #ff6a00;
747 }
748
749 .history-image {
750 width: 50px;
751 height: 50px;
752 border-radius: 8px;
753 object-fit: cover;
754 }
755
756 .history-info {
757 flex: 1;
758 }
759
760 .history-name {
761 font-weight: 600;
762 margin-bottom: 4px;
763 }
764
765 .history-bio {
766 font-size: 12px;
767 opacity: 0.8;
768 overflow: hidden;
769 text-overflow: ellipsis;
770 white-space: nowrap;
771 }
772
773 /* Scrollbar */
774 .tab-content::-webkit-scrollbar {
775 width: 6px;
776 }
777
778 .tab-content::-webkit-scrollbar-track {
779 background: rgba(255, 255, 255, 0.1);
780 border-radius: 10px;
781 }
782
783 .tab-content::-webkit-scrollbar-thumb {
784 background: rgba(255, 255, 255, 0.3);
785 border-radius: 10px;
786 }
787
788 .tab-content::-webkit-scrollbar-thumb:hover {
789 background: rgba(255, 255, 255, 0.5);
790 }
791
792 /* Filtered Words */
793 .filtered-words-list {
794 display: flex;
795 flex-wrap: wrap;
796 gap: 8px;
797 margin-top: 10px;
798 }
799
800 .filtered-word-tag {
801 background: rgba(255, 255, 255, 0.2);
802 padding: 6px 12px;
803 border-radius: 20px;
804 font-size: 12px;
805 display: flex;
806 align-items: center;
807 gap: 6px;
808 }
809
810 .remove-word-btn {
811 background: rgba(255, 255, 255, 0.3);
812 border: none;
813 color: white;
814 width: 18px;
815 height: 18px;
816 border-radius: 50%;
817 cursor: pointer;
818 font-size: 12px;
819 display: flex;
820 align-items: center;
821 justify-content: center;
822 }
823
824 .remove-word-btn:hover {
825 background: rgba(255, 255, 255, 0.5);
826 }
827
828 /* Save Button */
829 .save-button {
830 width: 100%;
831 padding: 12px;
832 background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);
833 border: none;
834 border-radius: 10px;
835 color: white;
836 font-size: 14px;
837 font-weight: 600;
838 cursor: pointer;
839 transition: all 0.3s;
840 margin-top: 15px;
841 }
842
843 .save-button:hover {
844 transform: translateY(-2px);
845 box-shadow: 0 8px 20px rgba(17, 153, 142, 0.4);
846 }
847
848 /* Message Templates */
849 .template-item {
850 background: rgba(255, 255, 255, 0.1);
851 padding: 10px;
852 border-radius: 8px;
853 margin-bottom: 10px;
854 display: flex;
855 justify-content: space-between;
856 align-items: center;
857 }
858
859 .template-text {
860 flex: 1;
861 font-size: 13px;
862 }
863
864 .delete-template-btn {
865 background: rgba(255, 255, 255, 0.2);
866 border: none;
867 color: white;
868 padding: 6px 12px;
869 border-radius: 6px;
870 cursor: pointer;
871 font-size: 12px;
872 }
873
874 .delete-template-btn:hover {
875 background: rgba(255, 255, 255, 0.3);
876 }
877 `;
878
879 TM_addStyle(styles);
880 }
881
882 function createUI() {
883 const panel = document.createElement('div');
884 panel.id = 'bumble-auto-swiper-panel';
885 panel.innerHTML = `
886 <div class="panel-content">
887 <div class="swiper-header">
888 <div class="swiper-title">Auto Swiper Pro</div>
889 <button class="minimize-btn" id="minimize-panel">−</button>
890 </div>
891
892 <div class="toggle-container">
893 <button class="toggle-button inactive" id="toggle-auto-swipe">
894 <span>Start Auto-Swipe</span>
895 </button>
896 </div>
897
898 <div class="stats-container">
899 <div class="stat-box">
900 <span class="stat-value" id="stat-total">0</span>
901 <span class="stat-label">Total Swipes</span>
902 </div>
903 <div class="stat-box">
904 <span class="stat-value" id="stat-right">0</span>
905 <span class="stat-label">Right Swipes</span>
906 </div>
907 <div class="stat-box">
908 <span class="stat-value" id="stat-left">0</span>
909 <span class="stat-label">Left Swipes</span>
910 </div>
911 <div class="stat-box">
912 <span class="stat-value" id="stat-matches">0</span>
913 <span class="stat-label">Matches</span>
914 </div>
915 </div>
916
917 <div class="tabs-container">
918 <div class="tabs-header">
919 <button class="tab-button active" data-tab="history">History</button>
920 <button class="tab-button" data-tab="settings">Settings</button>
921 <button class="tab-button" data-tab="messages">Messages</button>
922 </div>
923
924 <div class="tab-content active" id="tab-history">
925 <div class="tabs-header" style="margin-bottom: 10px;">
926 <button class="tab-button active" data-subtab="right">Right Swipes</button>
927 <button class="tab-button" data-subtab="left">Left Swipes</button>
928 </div>
929 <div id="history-right-content"></div>
930 <div id="history-left-content" style="display: none;"></div>
931 </div>
932
933 <div class="tab-content" id="tab-settings">
934 <div class="settings-group">
935 <label class="settings-label">Min Delay (ms)</label>
936 <input type="number" class="settings-input" id="setting-min-delay" value="2000" min="1000" max="10000">
937 </div>
938 <div class="settings-group">
939 <label class="settings-label">Max Delay (ms)</label>
940 <input type="number" class="settings-input" id="setting-max-delay" value="5000" min="2000" max="15000">
941 </div>
942 <div class="settings-group">
943 <label class="settings-label">Pause After Swipes</label>
944 <input type="number" class="settings-input" id="setting-pause-after" value="20" min="5" max="100">
945 </div>
946 <div class="checkbox-container">
947 <input type="checkbox" id="setting-verification-priority" checked>
948 <label class="settings-label" style="margin: 0;">Prioritize Verified Profiles</label>
949 </div>
950 <div class="settings-group">
951 <label class="settings-label">Filtered Words (comma separated)</label>
952 <input type="text" class="settings-input" id="filtered-words-input" placeholder="Add filtered word...">
953 <div class="filtered-words-list" id="filtered-words-list"></div>
954 </div>
955 <button class="save-button" id="save-settings">Save Settings</button>
956 </div>
957
958 <div class="tab-content" id="tab-messages">
959 <div class="checkbox-container">
960 <input type="checkbox" id="setting-auto-message">
961 <label class="settings-label" style="margin: 0;">Enable Auto-Messaging</label>
962 </div>
963 <div class="settings-group">
964 <label class="settings-label">Message Templates</label>
965 <input type="text" class="settings-input" id="new-template-input" placeholder="Add new template...">
966 <button class="save-button" id="add-template" style="margin-top: 10px;">Add Template</button>
967 </div>
968 <div id="templates-list" style="margin-top: 15px;"></div>
969 </div>
970 </div>
971 </div>
972 `;
973
974 document.body.appendChild(panel);
975 log('UI created successfully');
976
977 // Setup event listeners
978 setupEventListeners();
979
980 // Load initial data
981 loadInitialData();
982 }
983
984 function setupEventListeners() {
985 // Toggle auto-swipe
986 document.getElementById('toggle-auto-swipe').addEventListener('click', toggleAutoSwipe);
987
988 // Minimize panel
989 document.getElementById('minimize-panel').addEventListener('click', () => {
990 const panel = document.getElementById('bumble-auto-swiper-panel');
991 panel.classList.toggle('minimized');
992 });
993
994 // Tab switching
995 document.querySelectorAll('.tab-button[data-tab]').forEach(button => {
996 button.addEventListener('click', (e) => {
997 const tab = e.target.dataset.tab;
998
999 // Update tab buttons
1000 document.querySelectorAll('.tab-button[data-tab]').forEach(btn => btn.classList.remove('active'));
1001 e.target.classList.add('active');
1002
1003 // Update tab content
1004 document.querySelectorAll('.tab-content').forEach(content => content.classList.remove('active'));
1005 document.getElementById(`tab-${tab}`).classList.add('active');
1006 });
1007 });
1008
1009 // History subtabs
1010 document.querySelectorAll('.tab-button[data-subtab]').forEach(button => {
1011 button.addEventListener('click', (e) => {
1012 const subtab = e.target.dataset.subtab;
1013
1014 // Update subtab buttons
1015 document.querySelectorAll('.tab-button[data-subtab]').forEach(btn => btn.classList.remove('active'));
1016 e.target.classList.add('active');
1017
1018 // Update subtab content
1019 document.getElementById('history-right-content').style.display = subtab === 'right' ? 'block' : 'none';
1020 document.getElementById('history-left-content').style.display = subtab === 'left' ? 'block' : 'none';
1021 });
1022 });
1023
1024 // Save settings
1025 document.getElementById('save-settings').addEventListener('click', saveSettingsFromUI);
1026
1027 // Filtered words input
1028 document.getElementById('filtered-words-input').addEventListener('keypress', async (e) => {
1029 if (e.key === 'Enter') {
1030 const input = e.target;
1031 const word = input.value.trim();
1032 if (word) {
1033 const words = await loadFilteredWords();
1034 if (!words.includes(word)) {
1035 words.push(word);
1036 await saveFilteredWords(words);
1037 renderFilteredWords();
1038 }
1039 input.value = '';
1040 }
1041 }
1042 });
1043
1044 // Add message template
1045 document.getElementById('add-template').addEventListener('click', async () => {
1046 const input = document.getElementById('new-template-input');
1047 const template = input.value.trim();
1048 if (template) {
1049 const templates = await loadMessageTemplates();
1050 templates.push(template);
1051 await GM.setValue(CONFIG.STORAGE_KEYS.AUTO_MESSAGE_TEMPLATES, templates);
1052 renderMessageTemplates();
1053 input.value = '';
1054 }
1055 });
1056 }
1057
1058 async function loadInitialData() {
1059 // Load stats
1060 await loadStats();
1061 updateStatsDisplay();
1062
1063 // Load settings
1064 const settings = await loadSettings();
1065 document.getElementById('setting-min-delay').value = settings.minDelay || 2000;
1066 document.getElementById('setting-max-delay').value = settings.maxDelay || 5000;
1067 document.getElementById('setting-pause-after').value = settings.pauseAfterSwipes || 20;
1068 document.getElementById('setting-verification-priority').checked = settings.verificationPriority !== false;
1069 document.getElementById('setting-auto-message').checked = settings.autoMessageEnabled || false;
1070
1071 // Load filtered words
1072 renderFilteredWords();
1073
1074 // Load message templates
1075 renderMessageTemplates();
1076
1077 // Load history
1078 renderHistory();
1079
1080 // Check if auto-swipe was active
1081 const wasActive = await GM.getValue(CONFIG.STORAGE_KEYS.IS_ACTIVE, false);
1082 if (wasActive) {
1083 state.isActive = true;
1084 updateToggleButton();
1085 autoSwipeLoop();
1086 }
1087 }
1088
1089 function updateToggleButton() {
1090 const button = document.getElementById('toggle-auto-swipe');
1091 if (!button) return;
1092
1093 if (state.isPaused) {
1094 button.className = 'toggle-button paused';
1095 button.innerHTML = '<span>⏸️ Paused (Taking Break)</span>';
1096 } else if (state.isActive) {
1097 button.className = 'toggle-button active';
1098 button.innerHTML = '<span>✓ Auto-Swipe Active</span>';
1099 } else {
1100 button.className = 'toggle-button inactive';
1101 button.innerHTML = '<span>Start Auto-Swipe</span>';
1102 }
1103 }
1104
1105 function updateStatsDisplay() {
1106 document.getElementById('stat-total').textContent = state.stats.totalSwipes;
1107 document.getElementById('stat-right').textContent = state.stats.rightSwipes;
1108 document.getElementById('stat-left').textContent = state.stats.leftSwipes;
1109 document.getElementById('stat-matches').textContent = state.stats.matches;
1110 }
1111
1112 async function renderFilteredWords() {
1113 const words = await loadFilteredWords();
1114 const container = document.getElementById('filtered-words-list');
1115
1116 container.innerHTML = words.map(word => `
1117 <div class="filtered-word-tag">
1118 <span>${word}</span>
1119 <button class="remove-word-btn" data-word="${word}">×</button>
1120 </div>
1121 `).join('');
1122
1123 // Add remove listeners
1124 container.querySelectorAll('.remove-word-btn').forEach(btn => {
1125 btn.addEventListener('click', async (e) => {
1126 const word = e.target.dataset.word;
1127 const words = await loadFilteredWords();
1128 const filtered = words.filter(w => w !== word);
1129 await saveFilteredWords(filtered);
1130 renderFilteredWords();
1131 });
1132 });
1133 }
1134
1135 async function renderMessageTemplates() {
1136 const templates = await loadMessageTemplates();
1137 const container = document.getElementById('templates-list');
1138
1139 container.innerHTML = templates.map((template, index) => `
1140 <div class="template-item">
1141 <div class="template-text">${template}</div>
1142 <button class="delete-template-btn" data-index="${index}">Delete</button>
1143 </div>
1144 `).join('');
1145
1146 // Add delete listeners
1147 container.querySelectorAll('.delete-template-btn').forEach(btn => {
1148 btn.addEventListener('click', async (e) => {
1149 const index = parseInt(e.target.dataset.index);
1150 const templates = await loadMessageTemplates();
1151 templates.splice(index, 1);
1152 await GM.setValue(CONFIG.STORAGE_KEYS.AUTO_MESSAGE_TEMPLATES, templates);
1153 renderMessageTemplates();
1154 });
1155 });
1156 }
1157
1158 async function renderHistory() {
1159 const history = await loadSwipeHistory();
1160
1161 // Render right swipes
1162 const rightContainer = document.getElementById('history-right-content');
1163 rightContainer.innerHTML = history.right.slice(-20).reverse().map(profile => `
1164 <div class="history-item right">
1165 ${profile.imageUrl ? `<img src="${profile.imageUrl}" class="history-image" alt="${profile.name}">` : '<div class="history-image" style="background: rgba(255,255,255,0.2);"></div>'}
1166 <div class="history-info">
1167 <div class="history-name">${profile.name} ${profile.age}</div>
1168 <div class="history-bio">${profile.bio.substring(0, 50)}${profile.bio.length > 50 ? '...' : ''}</div>
1169 </div>
1170 </div>
1171 `).join('') || '<div style="text-align: center; opacity: 0.7;">No right swipes yet</div>';
1172
1173 // Render left swipes
1174 const leftContainer = document.getElementById('history-left-content');
1175 leftContainer.innerHTML = history.left.slice(-20).reverse().map(profile => `
1176 <div class="history-item left">
1177 ${profile.imageUrl ? `<img src="${profile.imageUrl}" class="history-image" alt="${profile.name}">` : '<div class="history-image" style="background: rgba(255,255,255,0.2);"></div>'}
1178 <div class="history-info">
1179 <div class="history-name">${profile.name} ${profile.age}</div>
1180 <div class="history-bio">${profile.bio.substring(0, 50)}${profile.bio.length > 50 ? '...' : ''}</div>
1181 </div>
1182 </div>
1183 `).join('') || '<div style="text-align: center; opacity: 0.7;">No left swipes yet</div>';
1184 }
1185
1186 async function saveSettingsFromUI() {
1187 const settings = {
1188 minDelay: parseInt(document.getElementById('setting-min-delay').value),
1189 maxDelay: parseInt(document.getElementById('setting-max-delay').value),
1190 pauseAfterSwipes: parseInt(document.getElementById('setting-pause-after').value),
1191 verificationPriority: document.getElementById('setting-verification-priority').checked,
1192 autoMessageEnabled: document.getElementById('setting-auto-message').checked
1193 };
1194
1195 await saveSettings(settings);
1196
1197 // Show success feedback
1198 const button = document.getElementById('save-settings');
1199 const originalText = button.textContent;
1200 button.textContent = '✓ Saved!';
1201 button.style.background = 'linear-gradient(135deg, #38ef7d 0%, #11998e 100%)';
1202
1203 setTimeout(() => {
1204 button.textContent = originalText;
1205 button.style.background = '';
1206 }, 2000);
1207 }
1208
1209 // ============================================
1210 // INITIALIZATION
1211 // ============================================
1212
1213 function init() {
1214 log('Bumble Auto Swiper Pro initializing...');
1215
1216 // Wait for page to be ready
1217 if (document.readyState === 'loading') {
1218 document.addEventListener('DOMContentLoaded', init);
1219 return;
1220 }
1221
1222 // Inject styles
1223 injectStyles();
1224
1225 // Create UI after a short delay to ensure page is loaded
1226 setTimeout(() => {
1227 createUI();
1228 log('Bumble Auto Swiper Pro initialized successfully');
1229 }, 2000);
1230
1231 // Setup MutationObserver to detect profile changes
1232 const observer = new MutationObserver(debounce(() => {
1233 if (state.isActive && !state.isPaused) {
1234 log('DOM mutation detected - profile may have changed');
1235 }
1236 }, 500));
1237
1238 observer.observe(document.body, {
1239 childList: true,
1240 subtree: true
1241 });
1242
1243 state.observer = observer;
1244 }
1245
1246 // Start the extension
1247 init();
1248
1249})();