Transform Snapchat web into mobile app experience on desktop with full mobile features and layout
Size
31.0 KB
Version
1.1.1
Created
Nov 18, 2025
Updated
24 days ago
1// ==UserScript==
2// @name Snapchat Mobile View for Desktop
3// @description Transform Snapchat web into mobile app experience on desktop with full mobile features and layout
4// @version 1.1.1
5// @match https://*.snapchat.com/*
6// @icon https://www.snapchat.com/web/version/22849690/favicon-badged.svg
7// @grant GM.getValue
8// @grant GM.setValue
9// @grant GM.xmlhttpRequest
10// ==/UserScript==
11(function() {
12 'use strict';
13
14 console.log('Snapchat Mobile View Extension: Initializing...');
15
16 // Mobile User Agent Configuration
17 const MOBILE_USER_AGENTS = {
18 ios: 'Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1',
19 android: 'Mozilla/5.0 (Linux; Android 13; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Mobile Safari/537.36'
20 };
21
22 // Override User Agent
23 function setMobileUserAgent() {
24 // Override navigator properties
25 Object.defineProperty(navigator, 'userAgent', {
26 get: function() { return MOBILE_USER_AGENTS.android; },
27 configurable: true
28 });
29
30 Object.defineProperty(navigator, 'platform', {
31 get: function() { return 'Linux armv8l'; },
32 configurable: true
33 });
34
35 Object.defineProperty(navigator, 'maxTouchPoints', {
36 get: function() { return 5; },
37 configurable: true
38 });
39
40 console.log('Snapchat Mobile View: User agent set to mobile');
41 }
42
43 // Apply Mobile Viewport and Styling
44 function applyMobileLayout() {
45 console.log('Snapchat Mobile View: Applying mobile layout...');
46
47 // Set viewport meta tag for mobile
48 let viewport = document.querySelector('meta[name="viewport"]');
49 if (!viewport) {
50 viewport = document.createElement('meta');
51 viewport.name = 'viewport';
52 document.head.appendChild(viewport);
53 }
54 viewport.content = 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no';
55
56 // Add mobile-optimized CSS
57 const mobileStyles = `
58 /* Mobile Container Wrapper */
59 body {
60 margin: 0 auto !important;
61 padding: 0 !important;
62 max-width: 430px !important;
63 background: #000 !important;
64 overflow-x: hidden !important;
65 }
66
67 html {
68 background: #1a1a1a !important;
69 overflow-x: hidden !important;
70 }
71
72 /* Center the mobile view */
73 #root {
74 max-width: 430px !important;
75 margin: 0 auto !important;
76 background: #000 !important;
77 min-height: 100vh !important;
78 position: relative !important;
79 }
80
81 /* Mobile-style main container */
82 main {
83 max-width: 430px !important;
84 margin: 0 auto !important;
85 }
86
87 /* Adjust layout for mobile view */
88 .Fpg8t {
89 display: flex !important;
90 flex-direction: column !important;
91 max-width: 430px !important;
92 }
93
94 /* Left sidebar - make it full width on mobile */
95 .BL7do {
96 width: 100% !important;
97 max-width: 430px !important;
98 min-width: unset !important;
99 border-right: none !important;
100 }
101
102 /* Main content area */
103 .Vbjsg {
104 width: 100% !important;
105 max-width: 430px !important;
106 position: relative !important;
107 }
108
109 /* Hide desktop-only elements */
110 .VqwkQ {
111 display: none !important;
112 }
113
114 /* Mobile-optimized buttons */
115 button {
116 -webkit-tap-highlight-color: rgba(255, 255, 255, 0.1) !important;
117 touch-action: manipulation !important;
118 }
119
120 /* Optimize touch targets */
121 button, a, input {
122 min-height: 44px !important;
123 min-width: 44px !important;
124 }
125
126 /* Mobile-friendly scrolling */
127 * {
128 -webkit-overflow-scrolling: touch !important;
129 }
130
131 /* Camera view optimization */
132 .ChLAv {
133 width: 100% !important;
134 max-width: 430px !important;
135 }
136
137 /* Search bar mobile styling */
138 ._S446 {
139 width: 100% !important;
140 padding: 8px 12px !important;
141 }
142
143 /* Chat list mobile optimization */
144 nav.deg2K {
145 width: 100% !important;
146 max-width: 430px !important;
147 }
148
149 /* Profile and settings */
150 .XlW_1 {
151 padding: 12px !important;
152 }
153
154 /* Mobile notification banner */
155 .wHvEy {
156 width: 100% !important;
157 padding: 16px !important;
158 }
159
160 /* Responsive images */
161 img {
162 max-width: 100% !important;
163 height: auto !important;
164 }
165
166 /* Mobile-style modals and overlays */
167 [role="dialog"] {
168 max-width: 430px !important;
169 margin: 0 auto !important;
170 }
171
172 /* Bottom navigation style (if present) */
173 .toJ1p {
174 display: flex !important;
175 justify-content: space-around !important;
176 padding: 12px !important;
177 }
178
179 /* Optimize for mobile gestures */
180 .swipeable {
181 touch-action: pan-y !important;
182 }
183
184 /* Hide scrollbars for cleaner mobile look */
185 ::-webkit-scrollbar {
186 width: 0px !important;
187 background: transparent !important;
188 }
189
190 /* Mobile-optimized input fields */
191 input[type="text"],
192 input[type="search"],
193 textarea {
194 font-size: 16px !important; /* Prevents zoom on iOS */
195 padding: 12px !important;
196 }
197
198 /* Status bar space (for mobile feel) */
199 body::before {
200 content: '';
201 display: block;
202 height: env(safe-area-inset-top, 0px);
203 background: #000;
204 }
205
206 /* Mobile-style animations */
207 * {
208 transition: transform 0.2s ease, opacity 0.2s ease !important;
209 }
210
211 /* Active state for touch feedback */
212 button:active,
213 a:active {
214 transform: scale(0.95) !important;
215 opacity: 0.8 !important;
216 }
217
218 /* Ensure full-screen camera */
219 ._HpJ5 {
220 width: 100% !important;
221 height: auto !important;
222 object-fit: cover !important;
223 }
224
225 /* Mobile-optimized lens carousel */
226 .O7Nhq {
227 overflow-x: auto !important;
228 -webkit-overflow-scrolling: touch !important;
229 }
230
231 /* Chat bubbles mobile style */
232 .message-bubble {
233 max-width: 80% !important;
234 word-wrap: break-word !important;
235 }
236 `;
237
238 TM_addStyle(mobileStyles);
239 console.log('Snapchat Mobile View: Mobile styles applied');
240 }
241
242 // Add mobile device simulation
243 function simulateMobileDevice() {
244 // Add touch event support
245 window.ontouchstart = function() {};
246
247 // Simulate mobile screen dimensions
248 Object.defineProperty(window.screen, 'width', {
249 get: function() { return 430; },
250 configurable: true
251 });
252
253 Object.defineProperty(window.screen, 'height', {
254 get: function() { return 932; },
255 configurable: true
256 });
257
258 console.log('Snapchat Mobile View: Mobile device simulation enabled');
259 }
260
261 // Add mobile-specific features
262 function addMobileFeatures() {
263 // Enable pull-to-refresh simulation
264 let startY = 0;
265 let isPulling = false;
266
267 document.addEventListener('touchstart', function(e) {
268 startY = e.touches[0].pageY;
269 isPulling = window.scrollY === 0;
270 }, { passive: true });
271
272 document.addEventListener('touchmove', function(e) {
273 if (isPulling && e.touches[0].pageY > startY + 100) {
274 console.log('Snapchat Mobile View: Pull to refresh triggered');
275 // Snapchat will handle the actual refresh
276 }
277 }, { passive: true });
278
279 console.log('Snapchat Mobile View: Mobile features added');
280 }
281
282 // Monitor for dynamic content and reapply styles
283 function observePageChanges() {
284 const observer = new MutationObserver(function() {
285 // Ensure mobile layout persists
286 const root = document.getElementById('root');
287 if (root && !root.style.maxWidth) {
288 applyMobileLayout();
289 }
290 });
291
292 observer.observe(document.body, {
293 childList: true,
294 subtree: true
295 });
296
297 console.log('Snapchat Mobile View: Page observer initialized');
298 }
299
300 // Intercept network requests to force mobile API
301 function interceptRequests() {
302 const originalFetch = window.fetch;
303 window.fetch = function(...args) {
304 const [url, options = {}] = args;
305
306 // Force mobile headers on all Snapchat API requests
307 if (typeof url === 'string' && url.includes('snapchat.com')) {
308 options.headers = options.headers || {};
309 options.headers['User-Agent'] = MOBILE_USER_AGENTS.android;
310 options.headers['X-Snapchat-Client-Type'] = 'mobile';
311
312 console.log('Snapchat Mobile View: Intercepted request to', url);
313 }
314
315 return originalFetch.apply(this, [url, options]);
316 };
317
318 // Intercept XMLHttpRequest
319 const originalOpen = XMLHttpRequest.prototype.open;
320 XMLHttpRequest.prototype.open = function(method, url, ...rest) {
321 this._url = url;
322 return originalOpen.apply(this, [method, url, ...rest]);
323 };
324
325 const originalSend = XMLHttpRequest.prototype.send;
326 XMLHttpRequest.prototype.send = function(...args) {
327 if (this._url && this._url.includes('snapchat.com')) {
328 this.setRequestHeader('User-Agent', MOBILE_USER_AGENTS.android);
329 this.setRequestHeader('X-Snapchat-Client-Type', 'mobile');
330 console.log('Snapchat Mobile View: Intercepted XHR to', this._url);
331 }
332 return originalSend.apply(this, args);
333 };
334
335 console.log('Snapchat Mobile View: Request interceptor enabled');
336 }
337
338 // Snap Download Feature
339 function addSnapDownloadFeature() {
340 console.log('Snapchat Mobile View: Adding snap download feature...');
341
342 // Create download button style
343 const downloadButtonStyle = `
344 .snap-download-btn {
345 position: fixed;
346 bottom: 80px;
347 right: 20px;
348 width: 56px;
349 height: 56px;
350 background: linear-gradient(135deg, #FFFC00 0%, #FFA500 100%);
351 border-radius: 50%;
352 border: none;
353 cursor: pointer;
354 z-index: 999998;
355 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
356 display: flex;
357 align-items: center;
358 justify-content: center;
359 font-size: 24px;
360 transition: transform 0.2s, box-shadow 0.2s;
361 }
362
363 .snap-download-btn:hover {
364 transform: scale(1.1);
365 box-shadow: 0 6px 16px rgba(0, 0, 0, 0.4);
366 }
367
368 .snap-download-btn:active {
369 transform: scale(0.95);
370 }
371
372 .snap-download-menu {
373 position: fixed;
374 bottom: 145px;
375 right: 20px;
376 background: rgba(0, 0, 0, 0.95);
377 border-radius: 16px;
378 padding: 16px;
379 z-index: 999997;
380 min-width: 200px;
381 box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);
382 display: none;
383 }
384
385 .snap-download-menu.active {
386 display: block;
387 animation: slideUp 0.3s ease;
388 }
389
390 @keyframes slideUp {
391 from {
392 opacity: 0;
393 transform: translateY(10px);
394 }
395 to {
396 opacity: 1;
397 transform: translateY(0);
398 }
399 }
400
401 .snap-download-item {
402 color: #fff;
403 padding: 12px;
404 margin: 4px 0;
405 border-radius: 8px;
406 cursor: pointer;
407 transition: background 0.2s;
408 font-size: 14px;
409 text-align: center;
410 }
411
412 .snap-download-item:hover {
413 background: rgba(255, 255, 255, 0.1);
414 }
415 `;
416
417 TM_addStyle(downloadButtonStyle);
418
419 // Create download button
420 const downloadBtn = document.createElement('button');
421 downloadBtn.className = 'snap-download-btn';
422 downloadBtn.innerHTML = '⬇️';
423 downloadBtn.title = 'Download Snaps';
424
425 // Create download menu
426 const downloadMenu = document.createElement('div');
427 downloadMenu.className = 'snap-download-menu';
428 downloadMenu.innerHTML = `
429 <div class="snap-download-item" data-action="download-current">Download Current Snap</div>
430 <div class="snap-download-item" data-action="download-all">Download All Visible Snaps</div>
431 <div class="snap-download-item" data-action="auto-save">Auto-Save: <span id="auto-save-status">OFF</span></div>
432 `;
433
434 document.body.appendChild(downloadBtn);
435 document.body.appendChild(downloadMenu);
436
437 // Toggle menu
438 downloadBtn.addEventListener('click', function() {
439 downloadMenu.classList.toggle('active');
440 });
441
442 // Download functionality
443 async function downloadSnap(imageUrl, filename) {
444 try {
445 const response = await GM.xmlhttpRequest({
446 method: 'GET',
447 url: imageUrl,
448 responseType: 'blob'
449 });
450
451 const blob = response.response;
452 const url = URL.createObjectURL(blob);
453 const a = document.createElement('a');
454 a.href = url;
455 a.download = filename || `snap_${Date.now()}.jpg`;
456 document.body.appendChild(a);
457 a.click();
458 document.body.removeChild(a);
459 URL.revokeObjectURL(url);
460
461 console.log('Snapchat Mobile View: Downloaded snap:', filename);
462 showNotification('✅ Snap downloaded successfully!');
463 } catch (error) {
464 console.error('Snapchat Mobile View: Download failed:', error);
465 showNotification('❌ Download failed. Try again.');
466 }
467 }
468
469 // Handle menu actions
470 downloadMenu.addEventListener('click', async function(e) {
471 const action = e.target.dataset.action;
472
473 if (action === 'download-current') {
474 const currentSnap = document.querySelector('img._HpJ5, video, img[src*="snap"]');
475 if (currentSnap) {
476 const imageUrl = currentSnap.src || currentSnap.currentSrc;
477 await downloadSnap(imageUrl, `snap_${Date.now()}.jpg`);
478 } else {
479 showNotification('⚠️ No snap found to download');
480 }
481 downloadMenu.classList.remove('active');
482 } else if (action === 'download-all') {
483 const allSnaps = document.querySelectorAll('img._HpJ5, img[src*="snap"], video');
484 if (allSnaps.length > 0) {
485 showNotification(`📥 Downloading ${allSnaps.length} snaps...`);
486 for (let i = 0; i < allSnaps.length; i++) {
487 const imageUrl = allSnaps[i].src || allSnaps[i].currentSrc;
488 await downloadSnap(imageUrl, `snap_${Date.now()}_${i}.jpg`);
489 await new Promise(resolve => setTimeout(resolve, 500));
490 }
491 showNotification('✅ All snaps downloaded!');
492 } else {
493 showNotification('⚠️ No snaps found to download');
494 }
495 downloadMenu.classList.remove('active');
496 } else if (action === 'auto-save') {
497 const currentStatus = await GM.getValue('auto_save_snaps', false);
498 const newStatus = !currentStatus;
499 await GM.setValue('auto_save_snaps', newStatus);
500 document.getElementById('auto-save-status').textContent = newStatus ? 'ON' : 'OFF';
501 showNotification(newStatus ? '✅ Auto-save enabled' : '⚠️ Auto-save disabled');
502
503 if (newStatus) {
504 startAutoSave();
505 }
506 }
507 });
508
509 // Auto-save functionality
510 async function startAutoSave() {
511 const autoSaveEnabled = await GM.getValue('auto_save_snaps', false);
512 if (!autoSaveEnabled) return;
513
514 const observer = new MutationObserver(async function() {
515 const newSnaps = document.querySelectorAll('img._HpJ5:not([data-saved]), img[src*="snap"]:not([data-saved])');
516 for (const snap of newSnaps) {
517 snap.setAttribute('data-saved', 'true');
518 const imageUrl = snap.src || snap.currentSrc;
519 if (imageUrl && !imageUrl.includes('data:')) {
520 await downloadSnap(imageUrl, `auto_snap_${Date.now()}.jpg`);
521 }
522 }
523 });
524
525 observer.observe(document.body, {
526 childList: true,
527 subtree: true
528 });
529 }
530
531 // Initialize auto-save if enabled
532 GM.getValue('auto_save_snaps', false).then(enabled => {
533 if (enabled) {
534 document.getElementById('auto-save-status').textContent = 'ON';
535 startAutoSave();
536 }
537 });
538
539 console.log('Snapchat Mobile View: Snap download feature added');
540 }
541
542 // Auto Snap Score Booster
543 function addAutoScoreBooster() {
544 console.log('Snapchat Mobile View: Adding auto score booster...');
545
546 // Create score booster button style
547 const boosterButtonStyle = `
548 .score-booster-btn {
549 position: fixed;
550 bottom: 150px;
551 right: 20px;
552 width: 56px;
553 height: 56px;
554 background: linear-gradient(135deg, #FF0080 0%, #7928CA 100%);
555 border-radius: 50%;
556 border: none;
557 cursor: pointer;
558 z-index: 999998;
559 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
560 display: flex;
561 align-items: center;
562 justify-content: center;
563 font-size: 24px;
564 transition: transform 0.2s, box-shadow 0.2s;
565 }
566
567 .score-booster-btn:hover {
568 transform: scale(1.1);
569 box-shadow: 0 6px 16px rgba(0, 0, 0, 0.4);
570 }
571
572 .score-booster-btn:active {
573 transform: scale(0.95);
574 }
575
576 .score-booster-menu {
577 position: fixed;
578 bottom: 215px;
579 right: 20px;
580 background: rgba(0, 0, 0, 0.95);
581 border-radius: 16px;
582 padding: 16px;
583 z-index: 999997;
584 min-width: 220px;
585 box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);
586 display: none;
587 }
588
589 .score-booster-menu.active {
590 display: block;
591 animation: slideUp 0.3s ease;
592 }
593
594 .score-booster-item {
595 color: #fff;
596 padding: 12px;
597 margin: 4px 0;
598 border-radius: 8px;
599 cursor: pointer;
600 transition: background 0.2s;
601 font-size: 14px;
602 text-align: center;
603 }
604
605 .score-booster-item:hover {
606 background: rgba(255, 255, 255, 0.1);
607 }
608
609 .score-status {
610 color: #FFFC00;
611 font-size: 12px;
612 text-align: center;
613 margin-top: 8px;
614 padding: 8px;
615 background: rgba(255, 252, 0, 0.1);
616 border-radius: 8px;
617 }
618 `;
619
620 TM_addStyle(boosterButtonStyle);
621
622 // Create booster button
623 const boosterBtn = document.createElement('button');
624 boosterBtn.className = 'score-booster-btn';
625 boosterBtn.innerHTML = '🚀';
626 boosterBtn.title = 'Auto Score Booster';
627
628 // Create booster menu
629 const boosterMenu = document.createElement('div');
630 boosterMenu.className = 'score-booster-menu';
631 boosterMenu.innerHTML = `
632 <div class="score-booster-item" data-action="start-boost">Start Auto Boost</div>
633 <div class="score-booster-item" data-action="stop-boost">Stop Auto Boost</div>
634 <div class="score-booster-item" data-action="send-streak">Send Streak Snaps</div>
635 <div class="score-status" id="boost-status">Status: Inactive</div>
636 `;
637
638 document.body.appendChild(boosterBtn);
639 document.body.appendChild(boosterMenu);
640
641 // Toggle menu
642 boosterBtn.addEventListener('click', function() {
643 boosterMenu.classList.toggle('active');
644 });
645
646 let boostInterval = null;
647
648 // Auto boost functionality
649 async function startAutoBoost() {
650 const boostStatus = document.getElementById('boost-status');
651 boostStatus.textContent = 'Status: Active 🟢';
652 showNotification('🚀 Auto score booster started!');
653
654 await GM.setValue('auto_boost_active', true);
655
656 boostInterval = setInterval(async function() {
657 try {
658 // Find camera button
659 const cameraBtn = document.querySelector('button.qJKfS, button[title*="Camera"]');
660 if (cameraBtn) {
661 cameraBtn.click();
662 console.log('Snapchat Mobile View: Camera clicked');
663
664 // Wait for camera to load
665 await new Promise(resolve => setTimeout(resolve, 2000));
666
667 // Find send button
668 const sendBtn = document.querySelector('button[title*="Send"], button.send-btn, button[aria-label*="Send"]');
669 if (sendBtn) {
670 sendBtn.click();
671 console.log('Snapchat Mobile View: Snap sent');
672
673 // Wait before next snap
674 await new Promise(resolve => setTimeout(resolve, 5000));
675 }
676 }
677 } catch (error) {
678 console.error('Snapchat Mobile View: Auto boost error:', error);
679 }
680 }, 30000); // Send snap every 30 seconds
681
682 console.log('Snapchat Mobile View: Auto boost started');
683 }
684
685 function stopAutoBoost() {
686 if (boostInterval) {
687 clearInterval(boostInterval);
688 boostInterval = null;
689 }
690
691 const boostStatus = document.getElementById('boost-status');
692 boostStatus.textContent = 'Status: Inactive 🔴';
693 showNotification('⚠️ Auto score booster stopped');
694
695 GM.setValue('auto_boost_active', false);
696 console.log('Snapchat Mobile View: Auto boost stopped');
697 }
698
699 async function sendStreakSnaps() {
700 showNotification('📸 Sending streak snaps...');
701
702 // Find all friends with streak
703 const streakFriends = document.querySelectorAll('[title*="streak"], [aria-label*="streak"]');
704
705 for (let i = 0; i < streakFriends.length; i++) {
706 try {
707 streakFriends[i].click();
708 await new Promise(resolve => setTimeout(resolve, 1000));
709
710 const cameraBtn = document.querySelector('button.qJKfS, button[title*="Camera"]');
711 if (cameraBtn) {
712 cameraBtn.click();
713 await new Promise(resolve => setTimeout(resolve, 2000));
714
715 const sendBtn = document.querySelector('button[title*="Send"]');
716 if (sendBtn) {
717 sendBtn.click();
718 await new Promise(resolve => setTimeout(resolve, 2000));
719 }
720 }
721 } catch (error) {
722 console.error('Snapchat Mobile View: Streak snap error:', error);
723 }
724 }
725
726 showNotification('✅ Streak snaps sent!');
727 }
728
729 // Handle menu actions
730 boosterMenu.addEventListener('click', function(e) {
731 const action = e.target.dataset.action;
732
733 if (action === 'start-boost') {
734 startAutoBoost();
735 boosterMenu.classList.remove('active');
736 } else if (action === 'stop-boost') {
737 stopAutoBoost();
738 boosterMenu.classList.remove('active');
739 } else if (action === 'send-streak') {
740 sendStreakSnaps();
741 boosterMenu.classList.remove('active');
742 }
743 });
744
745 // Check if auto boost was active
746 GM.getValue('auto_boost_active', false).then(active => {
747 if (active) {
748 startAutoBoost();
749 }
750 });
751
752 console.log('Snapchat Mobile View: Auto score booster added');
753 }
754
755 // Show notification helper
756 function showNotification(message) {
757 const notification = document.createElement('div');
758 notification.textContent = message;
759 notification.style.cssText = `
760 position: fixed;
761 top: 20px;
762 left: 50%;
763 transform: translateX(-50%);
764 background: rgba(0, 0, 0, 0.9);
765 color: #fff;
766 padding: 12px 24px;
767 border-radius: 24px;
768 font-size: 14px;
769 z-index: 9999999;
770 font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
771 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
772 animation: slideDown 0.3s ease;
773 `;
774
775 const style = document.createElement('style');
776 style.textContent = `
777 @keyframes slideDown {
778 from {
779 opacity: 0;
780 transform: translateX(-50%) translateY(-20px);
781 }
782 to {
783 opacity: 1;
784 transform: translateX(-50%) translateY(0);
785 }
786 }
787 `;
788 document.head.appendChild(style);
789
790 document.body.appendChild(notification);
791
792 setTimeout(() => {
793 notification.style.transition = 'opacity 0.3s';
794 notification.style.opacity = '0';
795 setTimeout(() => {
796 notification.remove();
797 style.remove();
798 }, 300);
799 }, 3000);
800 }
801
802 // Add mobile indicator
803 function addMobileIndicator() {
804 const indicator = document.createElement('div');
805 indicator.id = 'mobile-view-indicator';
806 indicator.innerHTML = '📱 Mobile View Active';
807 indicator.style.cssText = `
808 position: fixed;
809 bottom: 10px;
810 right: 10px;
811 background: rgba(0, 0, 0, 0.8);
812 color: #fff;
813 padding: 8px 12px;
814 border-radius: 20px;
815 font-size: 12px;
816 z-index: 999999;
817 font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
818 pointer-events: none;
819 opacity: 0.7;
820 `;
821
822 // Auto-hide after 3 seconds
823 setTimeout(() => {
824 if (indicator.parentNode) {
825 indicator.style.transition = 'opacity 0.5s';
826 indicator.style.opacity = '0';
827 setTimeout(() => indicator.remove(), 500);
828 }
829 }, 3000);
830
831 document.body.appendChild(indicator);
832 }
833
834 // Force reload with mobile user agent
835 async function forceReloadAsMobile() {
836 const hasReloaded = await GM.getValue('snapchat_mobile_reloaded', false);
837
838 if (!hasReloaded) {
839 console.log('Snapchat Mobile View: First load detected, reloading with mobile user agent...');
840 await GM.setValue('snapchat_mobile_reloaded', true);
841
842 // Set a timeout to reset the flag
843 setTimeout(async () => {
844 await GM.setValue('snapchat_mobile_reloaded', false);
845 }, 5000);
846
847 location.reload();
848 return true;
849 }
850
851 return false;
852 }
853
854 // Main initialization function
855 async function init() {
856 console.log('Snapchat Mobile View Extension: Starting initialization...');
857
858 // Set mobile user agent first
859 setMobileUserAgent();
860 simulateMobileDevice();
861 interceptRequests();
862
863 // Check if we need to reload
864 const needsReload = await forceReloadAsMobile();
865 if (needsReload) {
866 return; // Stop here, page will reload
867 }
868
869 // Wait for page to be ready
870 if (document.readyState === 'loading') {
871 document.addEventListener('DOMContentLoaded', function() {
872 applyMobileLayout();
873 addMobileFeatures();
874 observePageChanges();
875 addMobileIndicator();
876
877 // Add new features after a delay to ensure page is loaded
878 setTimeout(() => {
879 addSnapDownloadFeature();
880 addAutoScoreBooster();
881 }, 3000);
882 });
883 } else {
884 applyMobileLayout();
885 addMobileFeatures();
886 observePageChanges();
887 addMobileIndicator();
888
889 // Add new features after a delay to ensure page is loaded
890 setTimeout(() => {
891 addSnapDownloadFeature();
892 addAutoScoreBooster();
893 }, 3000);
894 }
895
896 console.log('Snapchat Mobile View Extension: Initialization complete!');
897 }
898
899 // Start the extension
900 init();
901
902})();