-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathGrok-Enhancer.user.js
More file actions
4387 lines (4015 loc) · 230 KB
/
Grok-Enhancer.user.js
File metadata and controls
4387 lines (4015 loc) · 230 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// ==UserScript==
// @name Grok Enhancer
// @namespace https://grok.com/
// @version 1.0
// @description All-in-one Grok enhancement
// @author Angel
// @match https://grok.com/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=grok.com
// @updateURL https://github.com/Angel2mp3/Grok-Enhancer/raw/main/Grok-Enhancer.user.js
// @downloadURL https://github.com/Angel2mp3/Grok-Enhancer/raw/main/Grok-Enhancer.user.js
// @grant unsafeWindow
// @grant GM_xmlhttpRequest
// @connect assets.grok.com
// @connect imagine-public.x.ai
// @run-at document-start
// ==/UserScript==
(function () {
'use strict';
if (window.location.hostname !== 'grok.com') return;
// ══════════════════════════════════════════════════════════════
// Shared Utilities & Globals
// ══════════════════════════════════════════════════════════════
const _win = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window;
const _originalFetch = _win.fetch.bind(_win);
const _OriginalWebSocket = _win.WebSocket;
const _encoder = new TextEncoder();
const _decoder = new TextDecoder();
// ── Media Database (populated from API interception for downloader) ──
const _ge_mediaDatabase = new Map();
function ge_extractPostId(url) {
if (!url) return null;
const matches = [...url.matchAll(/[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}/g)];
return matches.length > 0 ? matches[matches.length - 1][0] : null;
}
function ge_sanitizeFilename(str) {
return (str || '').replace(/[/\\?%*:|"<>]/g, '_').replace(/\s+/g, '_');
}
function ge_processApiMedia(apiData) {
if (!apiData?.posts) return;
for (const post of apiData.posts) {
if (!post.id) continue;
let entry = _ge_mediaDatabase.get(post.id);
if (!entry) entry = { id: post.id, items: [] };
function makeItem(src, fallback) {
const isVideo = src.mediaType === 'MEDIA_POST_TYPE_VIDEO';
const url = isVideo && src.hdMediaUrl ? src.hdMediaUrl : src.mediaUrl;
if (!url) return null;
const time = (src.createTime || fallback?.createTime || '').slice(0, 19).replace(/:/g, '-');
const model = src.modelName || fallback?.modelName || '';
const prompt = (src.originalPrompt || src.prompt || fallback?.originalPrompt || fallback?.prompt || '').trim();
let ext = isVideo ? 'mp4' : 'jpg';
if (src.mimeType === 'video/mp4') ext = 'mp4';
else if (src.mimeType === 'image/png') ext = 'png';
else if (src.mimeType === 'image/jpeg') ext = 'jpg';
let slug = ge_sanitizeFilename(prompt);
if (slug.length > 120) slug = slug.slice(0, 117) + '...';
return {
id: src.id, url, type: isVideo ? 'video' : 'image', ext,
name: `${time || 'unknown'}_${src.id}${model ? '_' + ge_sanitizeFilename(model) : ''}${slug ? '_' + slug : ''}.${ext}`,
thumb: src.mediaUrl || '', createTime: src.createTime || fallback?.createTime || '', prompt
};
}
if (post.mediaUrl) {
const item = makeItem(post, null);
if (item) entry.items.push(item);
}
if (post.childPosts?.length) {
for (const child of post.childPosts) {
const item = makeItem(child, post);
if (item) entry.items.push(item);
}
}
// Deduplicate by ID
const seen = new Set();
entry.items = entry.items.filter(i => { if (seen.has(i.id)) return false; seen.add(i.id); return true; });
if (entry.items.length > 0) {
_ge_mediaDatabase.set(post.id, entry);
for (const item of entry.items) {
if (item.id !== post.id) _ge_mediaDatabase.set(item.id, entry);
}
}
}
// Cap the in-memory media database to prevent unbounded growth in long sessions
if (_ge_mediaDatabase.size > 2000) {
const trimTo = 1000;
const keys = [..._ge_mediaDatabase.keys()];
for (let i = 0; i < keys.length - trimTo; i++) _ge_mediaDatabase.delete(keys[i]);
logDebug('[Downloader] Media database trimmed to', _ge_mediaDatabase.size, 'entries');
} else {
logDebug('[Downloader] Media database now has', _ge_mediaDatabase.size, 'entries');
}
}
function getState(key, def) {
try {
const v = localStorage.getItem(key);
if (v === null) return def;
if (v === 'true') return true;
if (v === 'false') return false;
return JSON.parse(v);
} catch (_) { return def; }
}
function setState(key, val) {
try { localStorage.setItem(key, typeof val === 'boolean' ? String(val) : JSON.stringify(val)); }
catch (_) { /* ignore */ }
}
// ── Feature toggles ──────────────────────────────────────────
let featureLogo = getState('GrokEnhancer_Logo', true);
let featureLinks = getState('GrokEnhancer_Links', true);
let featureDeMod = getState('GrokDeModEnabled', true);
let featureRateLimit = getState('GrokEnhancer_RateLimit', true);
let featureDebug = getState('GrokDeModDebug', false);
let featureHideShare = getState('GrokEnhancer_HideShare', false);
let featureDeleter = getState('GrokEnhancer_Deleter', true);
let featureHidePopups = getState('GrokEnhancer_HidePopups', false);
let featureHidePremium = getState('GrokEnhancer_HidePremium', false);
let featureHideHeavy = getState('GrokEnhancer_HideHeavy', false);
let featureAutoPrivate = getState('GrokEnhancer_AutoPrivate', false);
let featureStreamer = getState('GrokEnhancer_Streamer', false);
let ge_activeStyleId = getState('GrokEnhancer_ActiveStyleId', null);
// ── Imagine Menu state ──
let featureImagineMenu = getState('GrokEnhancer_ImagineMenu', false);
let ge_imInterceptOn = getState('GrokEnhancer_IM_Intercept', true);
let ge_imVideoLength = parseInt(getState('GrokEnhancer_IM_VideoLength', '30')) || 30;
let ge_imAutoRetry = getState('GrokEnhancer_IM_AutoRetry', false);
let ge_imMaxRetries = parseInt(getState('GrokEnhancer_IM_MaxRetries', '3')) || 3;
let ge_imDisableLoop = getState('GrokEnhancer_IM_DisableLoop', false);
let ge_imHideOverlay = getState('GrokEnhancer_IM_HideOverlay', false);
let ge_imSmartRetry = getState('GrokEnhancer_IM_SmartRetry', false);
let ge_imPersistentPrompt = getState('GrokEnhancer_IM_PersistentPrompt', false);
let featureDisableAutoScroll = getState('GrokEnhancer_DisableAutoScroll', false);
let ge_imInterceptCount = 0;
let ge_imRetryCount = 0;
let ge_imLastRetryTime = 0;
let ge_imActivePromptId = getState('GrokEnhancer_ActivePromptId', null);
// ── Prompt Manager helpers ──
function ge_getPrompts() {
try { return JSON.parse(localStorage.getItem('GrokEnhancer_Prompts') || '[]'); }
catch (_) { return []; }
}
function ge_savePrompts(p) { localStorage.setItem('GrokEnhancer_Prompts', JSON.stringify(p)); }
function logDebug(...a) { if (featureDebug) console.log('[GrokEnhancer]', ...a); }
function logError(...a) { console.error('[GrokEnhancer]', ...a); }
// ── FAB triple-click hide/show ──────────────────────────────
let _ge_fabHidden = getState('GrokEnhancer_FabHidden', false);
let _ge_fabClicks = [];
const GE_TRIPLE_CLICK_MS = 500; // max time window for 3 clicks
// ══════════════════════════════════════════════════════════════
// 1. SuperGrok Logo Replacement
// ══════════════════════════════════════════════════════════════
const SUPERGROK_VIEWBOX = '0 0 149 33';
const SUPERGROK_INNER_HTML = `<path id="mark" d="M24.3187 12.8506L13.2371 21.0407L29.1114 5.07631V5.09055L33.6964 0.5C33.6139 0.616757 33.5315 0.730667 33.449 0.844576C29.9647 5.64871 28.2637 7.99809 29.629 13.8758L29.6205 13.8673C30.562 17.8683 29.5551 22.3051 26.304 25.5601C22.2053 29.6665 15.6463 30.5806 10.2449 26.8843L14.0108 25.1386C17.4581 26.4941 21.2297 25.899 23.9404 23.1851C26.651 20.4712 27.2597 16.5185 25.8973 13.2294C25.6384 12.6057 24.8619 12.4491 24.3187 12.8506Z" fill="currentColor"/>
<path id="mark" d="M11.0498 10.2763C7.74186 13.5853 7.07344 19.3235 10.9503 23.0313L10.9474 23.0341L0.363647 32.5C1.02597 31.5868 1.84612 30.7235 2.66565 29.8609L2.66566 29.8609L2.69885 29.826L2.70569 29.8188C5.04711 27.3551 7.36787 24.9131 5.94992 21.4622C4.04991 16.8403 5.15635 11.4239 8.6748 7.90126C12.3326 4.24192 17.7198 3.31926 22.2195 5.17313C23.215 5.54334 24.0826 6.07017 24.7595 6.55998L21.0022 8.2971C17.5036 6.82767 13.4959 7.82722 11.0498 10.2763Z" fill="currentColor"/>
<path d="M37.8333 19.3306C38.0527 22.2268 40.2688 24.5525 44.5254 24.5525C48.2114 24.5525 50.8663 22.7753 50.8663 19.8352C50.8663 17.2462 49.111 16.0394 46.0612 15.3592L43.6477 14.7888C41.8705 14.3938 40.9051 13.7575 40.9051 12.6166C40.9051 11.2124 42.2435 10.3128 44.1962 10.3128C46.0832 10.3128 47.4655 11.1466 47.6849 13.2748H50.3836C50.2081 10.1373 47.7726 8.09674 44.1962 8.09674C40.6637 8.09674 38.2502 10.0056 38.2502 12.7921C38.2502 15.7761 40.7954 16.6976 43.0772 17.2242L45.4688 17.7508C47.3777 18.1896 48.1456 18.9795 48.1456 20.0107C48.1456 21.6124 46.5439 22.3146 44.5254 22.3146C42.3971 22.3146 40.9051 21.5027 40.576 19.3306H37.8333Z" fill="currentColor"/>
<path d="M56.9253 24.399C54.0071 24.399 53.0198 22.6876 53.0198 20.274V12.7921H55.4991V20.1424C55.4991 21.4369 56.2451 22.2926 57.5616 22.2926C59.5582 22.2926 60.5456 20.8006 60.5456 18.8917V12.7921H63.0249V24.1357H60.6553V22.2048H60.6114C59.8215 23.7188 58.5709 24.399 56.9253 24.399Z" fill="currentColor"/>
<path d="M65.3942 12.7921V28.48H67.8736V22.5998H67.9394C68.7293 23.9163 70.2651 24.399 71.428 24.399C74.7631 24.399 76.5403 21.6783 76.5403 18.4529C76.5403 15.2276 74.7631 12.5069 71.428 12.5069C70.2651 12.5069 68.7293 12.9896 67.9394 14.3061H67.8736V12.7921H65.3942ZM70.8795 22.3365C68.7073 22.3365 67.8077 20.4057 67.8077 18.4529C67.8077 16.4343 68.7073 14.5474 70.8795 14.5474C73.0955 14.5474 73.9512 16.4343 73.9512 18.4529C73.9512 20.4057 73.0955 22.3365 70.8795 22.3365Z" fill="currentColor"/>
<path d="M83.4145 24.399C79.8601 24.399 77.8415 21.8977 77.8415 18.4529C77.8415 14.9204 79.8601 12.5069 83.217 12.5069C86.4863 12.5069 88.4829 14.7229 88.5926 18.1458L87.5175 19.155H80.3647C80.5622 21.1736 81.6373 22.3804 83.4145 22.3804C84.709 22.3804 85.6306 21.7002 86.0913 20.4496H88.5487C87.9782 23.0605 86.0474 24.399 83.4145 24.399ZM80.4305 17.3778H86.1352C85.8719 15.447 84.7529 14.4597 83.217 14.4597C81.8128 14.4597 80.7377 15.5348 80.4305 17.3778Z" fill="currentColor"/>
<path d="M90.4185 14.5913V24.1357H92.8979V14.8985H96.935V12.7921H92.5029L90.4185 14.5913Z" fill="currentColor"/>
<path d="M106.565 24.4252C101.684 24.4252 98.7743 20.9105 98.7743 16.1179C98.7743 11.2801 101.788 7.67999 106.66 7.67999C110.468 7.67999 113.255 9.61507 113.912 13.2152H110.989C110.558 11.1677 108.836 10.0201 106.66 10.0201C103.148 10.0201 101.607 13.0352 101.607 16.1179C101.607 19.2005 103.148 22.193 106.66 22.193C110.014 22.193 111.487 19.7854 111.601 17.7829H106.547V15.453H114.184L114.172 16.6712C114.172 21.1975 112.311 24.4252 106.565 24.4252Z" fill="currentColor"/>
<path d="M116.359 14.34V24.1279H118.919V14.6551H123.089V12.495H118.511L116.359 14.34ZM129.354 24.3976C125.547 24.3976 123.485 21.72 123.485 18.2999C123.485 14.8572 125.547 12.2021 129.354 12.2021C133.184 12.2021 135.223 14.8572 135.223 18.2999C135.223 21.72 133.184 24.3976 129.354 24.3976ZM126.159 18.2999C126.159 20.955 127.609 22.2826 129.354 22.2826C131.122 22.2826 132.549 20.955 132.549 18.2999C132.549 15.6449 131.122 14.2948 129.354 14.2948C127.609 14.2948 126.159 15.6449 126.159 18.2999Z" fill="currentColor"/>
<path d="M137.117 24.1287V8.06312H139.678V18.6658V24.1287H137.117ZM139.678 18.6658L145.094 12.4958H148.199L143.326 17.7836L148.244 24.1287H145.185L141.202 18.6766L139.678 18.6658Z" fill="currentColor"/>
<defs>
<filter id="filter0_i_140_136" x="0.363647" y="0.5" width="147.88" height="32" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="0.409854"/>
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
<feColorMatrix type="matrix" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.2 0"/>
<feBlend mode="normal" in2="shape" result="effect1_innerShadow_140_136"/>
</filter>
</defs>`;
let logoReplaced = false;
let _logoSvgObserver = null;
let lastUrl = location.href;
new MutationObserver(() => {
if (location.href !== lastUrl) {
lastUrl = location.href;
logoReplaced = false;
}
}).observe(document, { subtree: true, childList: true });
function isGreetingLogo(svg) {
let el = svg.parentElement;
while (el && el !== document.body) {
if (el.classList && el.classList.contains('max-w-breakout')) return true;
el = el.parentElement;
}
return false;
}
function tryReplaceLogo() {
if (!featureLogo || logoReplaced) return;
const svgs = document.querySelectorAll('svg');
for (const svg of svgs) {
const markPaths = svg.querySelectorAll('path[id="mark"]');
if (markPaths.length < 2) continue;
if (!isGreetingLogo(svg)) continue;
if (svg.getAttribute('viewBox') === SUPERGROK_VIEWBOX) { logoReplaced = true; return; }
svg.setAttribute('viewBox', SUPERGROK_VIEWBOX);
svg.setAttribute('fill-rule', 'evenodd');
svg.setAttribute('clip-rule', 'evenodd');
svg.innerHTML = SUPERGROK_INNER_HTML;
logoReplaced = true;
// Watch this SVG: if React re-renders and resets it, re-allow replacement
if (_logoSvgObserver) _logoSvgObserver.disconnect();
_logoSvgObserver = new MutationObserver(() => {
if (svg.getAttribute('viewBox') !== SUPERGROK_VIEWBOX) {
logoReplaced = false;
}
});
_logoSvgObserver.observe(svg, { attributes: true, attributeFilter: ['viewBox'], childList: true });
break;
}
}
// ══════════════════════════════════════════════════════════════
// 2. Clickable Links
// ══════════════════════════════════════════════════════════════
const SCAN_RE = /(?<![a-zA-Z0-9.@])@([A-Za-z0-9_]{1,15})\b|https?:\/\/[^\s<>"'`\])\}]+|\bwww\.[a-zA-Z0-9\-]+\.[^\s<>"'`\])\}]+|\b(?:[a-zA-Z0-9\-]+\.)+(?:com|org|net|io|dev|app|co|ai|gov|edu|me|info|xyz|biz|name|mobi|pro|tel|jobs|museum|coop|aero|int|travel|post|tech|software|online|site|website|store|shop|blog|cloud|digital|media|network|solutions|services|company|agency|studio|design|systems|consulting|management|marketing|finance|health|care|technology|tools|space|zone|world|life|live|social|community|group|team|global|business|professional|expert|plus|city|land|today|news|press|review|guide|support|help|training|education|academy|institute|center|foundation|ventures|capital|partners|holdings|works|build|engineering|energy|eco|farm|food|restaurant|bar|hotel|tours|rentals|properties|estate|homes|auto|cars|sports|fitness|art|gallery|photography|video|music|show|film|events|party|fun|games|game|play|dating|love|wedding|family|kids|pet|clinic|dental|doctor|pharmacy|insurance|loans|credit|bank|money|pay|law|attorney|legal|security|repair|cleaning|run|link|click|host|page|web|email|uk|ca|au|de|fr|jp|ru|br|in|it|es|nl|se|no|fi|dk|pl|pt|be|ch|at|nz|mx|ar|sg|hk|tw|kr|za|ie|cz|hu|ro|gr|th|vn|ph|id|my|ng|ke|gg|re|tv|cc|so|is|ee|lv|lt|sk|si|hr|rs|bg|mk|al|ba|md|ge|am|az|by|kz|ua|uz|mn|af|pk|bd|lk|np|mm|kh|la|bn|pg|fj|ws|to|vu|ki|fm|pw|mh|nr|sb|eu|us|gb|il|tr|sa|ae|eg|ma|li|lu|mo|mt|cy|gh|tz|sn|cm|ao|zw|mu|bw|na|ls|sz|rw|sd|ly|dz|cd|ga|gq|cv|sl|lr|gn|bf|ml|gm|mz|sc|sh|je|im|gi|gt|bz|sv|hn|ni|cr|pa|pe|cl|bo|uy|py|ec|tt|jm|cu|do|ht|dm|bb|lc|vc|gd|ag|kn|pr|vi|ky|bm|aw|gp|mq|nc|pf|as|gu|ck|nu|tk|nf|cx|sj|gl|pm|yt|ax|fo)(?:\/[^\s<>"'`\])\}]*)?\b/gi;
// Platform context detection for @mention link routing
const PLATFORM_PATTERNS = [
{ re: /\b(instagram|insta)\b/i, url: u => `https://instagram.com/${u}` },
{ re: /\b(tiktok|tik\s*tok|\bTT\b)\b/i, url: u => `https://tiktok.com/@${u}` },
{ re: /\b(snapchat|snap)\b/i, url: u => `https://snapchat.com/add/${u}` },
{ re: /\b(bluesky|bsky\.app)\b/i, url: u => `https://bsky.app/profile/${u}` },
{ re: /\b(threads\.net|threads)\b/i, url: u => `https://threads.net/@${u}` },
{ re: /\btwitch\b/i, url: u => `https://twitch.tv/${u}` },
{ re: /\bkick\.com|\bkick\b/i, url: u => `https://kick.com/${u}` },
{ re: /\byoutube\b/i, url: u => `https://youtube.com/@${u}` },
{ re: /\b(facebook|fb\.com)\b/i, url: u => `https://facebook.com/${u}` },
{ re: /\blinkedin\b/i, url: u => `https://linkedin.com/in/${u}` },
{ re: /\b(github|gh\b)\b/i, url: u => `https://github.com/${u}` },
{ re: /\b(telegram|t\.me)\b/i, url: u => `https://t.me/${u}` },
{ re: /\bsoundcloud\b/i, url: u => `https://soundcloud.com/${u}` },
{ re: /\bspotify\b/i, url: u => `https://open.spotify.com/user/${u}` },
{ re: /\bmedium\b/i, url: u => `https://medium.com/@${u}` },
{ re: /\bsubstack\b/i, url: u => `https://${u}.substack.com` },
{ re: /\bpatreon\b/i, url: u => `https://patreon.com/${u}` },
{ re: /\bko-?fi\b/i, url: u => `https://ko-fi.com/${u}` },
{ re: /\bvsco\b/i, url: u => `https://vsco.co/${u}` },
{ re: /\bpinterest\b/i, url: u => `https://pinterest.com/${u}` },
{ re: /\btumblr\b/i, url: u => `https://tumblr.com/${u}` },
{ re: /\breddit\b/i, url: u => `https://reddit.com/user/${u}` },
{ re: /\bmastodon\b/i, url: u => `https://mastodon.social/@${u}` },
{ re: /\bdiscord\b/i, url: u => `https://discord.com/users/${u}` },
{ re: /\b(x\.com|twitter|tweet|retweet|x account|on x)\b/i, url: u => `https://x.com/${u}` },
];
function getMentionHref(user, text, start, textNode) {
const WIN = 150;
const mentionStr = '@' + user;
// Use the full block element text so that context in sibling nodes is included
const ctxEl = textNode?.parentElement?.closest('p,li,div,article,section,blockquote,td') || textNode?.parentElement;
const fullText = (ctxEl && ctxEl.textContent.length > text.length) ? ctxEl.textContent : text;
// Find where this text node sits inside the block text, then add the local match offset
let baseOffset = 0;
if (fullText !== text) {
const idx = fullText.indexOf(text);
if (idx !== -1) baseOffset = idx;
}
const mentionIdx = baseOffset + start;
const winStart = Math.max(0, mentionIdx - WIN);
const winEnd = Math.min(fullText.length, mentionIdx + mentionStr.length + WIN);
const win = fullText.slice(winStart, winEnd);
const mentionPos = mentionIdx - winStart;
const atEnd = mentionPos + mentionStr.length;
let bestPlatform = null;
let bestDist = Infinity;
for (const p of PLATFORM_PATTERNS) {
const re = new RegExp(p.re.source, 'gi');
let m;
while ((m = re.exec(win)) !== null) {
const kwEnd = m.index + m[0].length;
const dist = kwEnd <= mentionPos ? mentionPos - kwEnd
: m.index >= atEnd ? m.index - atEnd
: 0;
if (dist < bestDist) { bestDist = dist; bestPlatform = p; }
}
}
return bestPlatform ? bestPlatform.url(user) : 'https://x.com/' + user;
}
const SKIP_TAGS = new Set([
'A', 'SCRIPT', 'STYLE', 'CODE', 'PRE', 'TEXTAREA', 'INPUT', 'SELECT',
'BUTTON', 'NOSCRIPT', 'IFRAME', 'OBJECT', 'SVG',
]);
const PROCESSED_ATTR = 'data-linkified';
function linkifyNode(root) {
if (!featureLinks) return;
const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, {
acceptNode(node) {
let el = node.parentElement;
while (el) {
if (SKIP_TAGS.has(el.tagName)) return NodeFilter.FILTER_REJECT;
if (el.hasAttribute(PROCESSED_ATTR)) return NodeFilter.FILTER_REJECT;
el = el.parentElement;
}
if (!node.nodeValue || node.nodeValue.trim().length < 4) return NodeFilter.FILTER_SKIP;
return NodeFilter.FILTER_ACCEPT;
}
});
const textNodes = [];
let n;
while ((n = walker.nextNode())) textNodes.push(n);
for (const textNode of textNodes) {
const text = textNode.nodeValue;
SCAN_RE.lastIndex = 0;
if (!SCAN_RE.test(text)) continue;
SCAN_RE.lastIndex = 0;
const frag = document.createDocumentFragment();
let lastIndex = 0, match;
while ((match = SCAN_RE.exec(text)) !== null) {
const full = match[0], mentionUser = match[1], start = match.index;
if (start > lastIndex) frag.appendChild(document.createTextNode(text.slice(lastIndex, start)));
const a = document.createElement('a');
if (mentionUser) {
a.href = getMentionHref(mentionUser, text, start, textNode);
} else {
a.href = /^https?:\/\//i.test(full) ? full : 'https://' + full;
}
a.textContent = full;
a.target = '_blank';
a.rel = 'noopener noreferrer';
a.className = 'ge-link';
a.style.cssText = 'text-decoration:underline;cursor:pointer;';
frag.appendChild(a);
lastIndex = start + full.length;
}
if (lastIndex < text.length) frag.appendChild(document.createTextNode(text.slice(lastIndex)));
const parent = textNode.parentElement;
if (parent) parent.setAttribute(PROCESSED_ATTR, '1');
textNode.parentNode.replaceChild(frag, textNode);
}
}
// ══════════════════════════════════════════════════════════════
// 3. DeMod — Moderation Bypass
// ══════════════════════════════════════════════════════════════
const DEMOD_CONFIG = {
defaultFlags: ['isFlagged', 'isBlocked', 'moderationApplied', 'restricted'],
messageKeys: ['message', 'content', 'text', 'error'],
moderationMessagePatterns: [
/this content has been moderated/i,
/sorry, i cannot assist/i,
/policy violation/i,
/blocked/i,
/moderated/i,
/restricted/i,
/content restricted/i,
/unable to process/i,
/cannot help/i,
/(sorry|apologies).*?(cannot|unable|help|assist)/i,
],
clearedMessageText: '[Content cleared by Grok DeMod]',
recoveryTimeoutMs: 5000,
statusColors: {
safe: '#66ff66',
flagged: '#ffa500',
blocked: '#ff6666',
recovering: '#ffcc00',
},
};
let moderationFlags = getState('GrokDeModFlags', DEMOD_CONFIG.defaultFlags);
let demodInitCache = null;
let currentConversationId = null;
const ModerationResult = Object.freeze({ SAFE: 0, FLAGGED: 1, BLOCKED: 2 });
function timeoutPromise(ms, promise, desc = 'Promise') {
return new Promise((resolve, reject) => {
const timer = setTimeout(() => reject(new Error(`Timeout (${desc})`)), ms);
promise.then(v => { clearTimeout(timer); resolve(v); }, e => { clearTimeout(timer); reject(e); });
});
}
function getModerationResult(obj, path = '') {
if (typeof obj !== 'object' || obj === null) return ModerationResult.SAFE;
let result = ModerationResult.SAFE;
for (const key in obj) {
if (!obj.hasOwnProperty(key)) continue;
const cp = path ? `${path}.${key}` : key;
const value = obj[key];
if (key === 'isBlocked' && value === true) { logDebug(`Blocked: '${cp}'`); return ModerationResult.BLOCKED; }
if (moderationFlags.includes(key) && value === true) { logDebug(`Flagged: '${cp}'`); result = Math.max(result, ModerationResult.FLAGGED); }
if (DEMOD_CONFIG.messageKeys.includes(key) && typeof value === 'string') {
const c = value.toLowerCase();
for (const p of DEMOD_CONFIG.moderationMessagePatterns) {
if (p.test(c)) {
if (/blocked|moderated|restricted/i.test(p.source)) return ModerationResult.BLOCKED;
result = Math.max(result, ModerationResult.FLAGGED);
break;
}
}
if (result === ModerationResult.SAFE && c.length < 70 && /(sorry|apologies|unable|cannot)/i.test(c))
result = Math.max(result, ModerationResult.FLAGGED);
}
if (typeof value === 'object') {
const cr = getModerationResult(value, cp);
if (cr === ModerationResult.BLOCKED) return ModerationResult.BLOCKED;
result = Math.max(result, cr);
}
}
return result;
}
function clearFlagging(obj) {
if (typeof obj !== 'object' || obj === null) return obj;
if (Array.isArray(obj)) return obj.map(clearFlagging);
const out = {};
for (const key in obj) {
if (!obj.hasOwnProperty(key)) continue;
const v = obj[key];
if (moderationFlags.includes(key) && v === true) { out[key] = false; }
else if (DEMOD_CONFIG.messageKeys.includes(key) && typeof v === 'string') {
let replaced = false;
for (const p of DEMOD_CONFIG.moderationMessagePatterns) {
if (p.test(v)) { out[key] = DEMOD_CONFIG.clearedMessageText; replaced = true; break; }
}
if (!replaced && v.length < 70 && /(sorry|apologies|unable|cannot)/i.test(v.toLowerCase())) {
if (getModerationResult({ [key]: v }) === ModerationResult.FLAGGED) { out[key] = DEMOD_CONFIG.clearedMessageText; replaced = true; }
}
if (!replaced) out[key] = v;
}
else if (typeof v === 'object') out[key] = clearFlagging(v);
else out[key] = v;
}
return out;
}
function extractConversationIdFromUrl(url) {
const m = url.match(/\/conversation\/([a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})/i);
return m ? m[1] : null;
}
async function redownloadLatestMessage() {
if (!currentConversationId) { panelAddLog('Recovery failed: No conversation ID.'); return null; }
if (!demodInitCache || !demodInitCache.headers) {
try {
const r = await _originalFetch(`/rest/app-chat/conversation/${currentConversationId}`, { method: 'GET', headers: { 'Accept': 'application/json' } });
if (r.ok) demodInitCache = { headers: new Headers({ 'Accept': 'application/json' }), credentials: 'include' };
else { panelAddLog('Recovery failed: Cannot get request data.'); return null; }
} catch (_) { panelAddLog('Recovery failed: Error getting request data.'); return null; }
}
panelAddLog('Attempting content recovery...');
const headers = new Headers(demodInitCache.headers);
if (!headers.has('Accept')) headers.set('Accept', 'application/json, text/plain, */*');
try {
const resp = await timeoutPromise(DEMOD_CONFIG.recoveryTimeoutMs,
_originalFetch(`/rest/app-chat/conversation/${currentConversationId}`, { method: 'GET', headers, credentials: demodInitCache.credentials || 'include' }),
'Recovery Fetch');
if (!resp.ok) { panelAddLog(`Recovery failed: HTTP ${resp.status}`); return null; }
const data = await resp.json();
const msgs = data?.messages;
if (!Array.isArray(msgs) || msgs.length === 0) { panelAddLog('Recovery failed: No messages found.'); return null; }
msgs.sort((a, b) => (b.timestamp ? new Date(b.timestamp).getTime() : 0) - (a.timestamp ? new Date(a.timestamp).getTime() : 0));
const latest = msgs[0];
if (!latest || typeof latest.content !== 'string' || !latest.content.trim()) { panelAddLog('Recovery failed: Invalid latest message.'); return null; }
panelAddLog('Recovery seems successful.');
return { content: latest.content };
} catch (e) { panelAddLog(`Recovery error: ${e.message}`); return null; }
}
async function processPotentialModeration(json, source) {
const mr = getModerationResult(json);
let out = json;
if (mr !== ModerationResult.SAFE) {
if (mr === ModerationResult.BLOCKED) {
panelAddLog(`Blocked content from ${source}.`);
panelUpdateStatus(mr, true);
const recovered = await redownloadLatestMessage();
if (recovered && recovered.content) {
panelAddLog(`Recovery successful (${source}).`);
let replaced = false;
for (const k of [...DEMOD_CONFIG.messageKeys, 'text', 'message']) {
if (typeof out[k] === 'string') { out[k] = recovered.content; replaced = true; break; }
}
if (!replaced) out.recovered_content = recovered.content;
out = clearFlagging(out);
panelUpdateStatus(mr, false);
} else {
panelAddLog(`Recovery failed (${source}).`);
out = clearFlagging(json);
panelUpdateStatus(mr, false);
}
} else {
panelAddLog(`Flagged content cleared (${source}).`);
out = clearFlagging(json);
panelUpdateStatus(mr);
}
} else {
if (panelStatusEl && !panelStatusEl.textContent.includes('Blocked') && !panelStatusEl.textContent.includes('Flagged') && !panelStatusEl.textContent.includes('Recovering'))
panelUpdateStatus(mr);
else if (panelStatusEl && panelStatusEl.textContent.includes('Recovering'))
panelUpdateStatus(ModerationResult.SAFE);
}
return out;
}
async function handleFetchResponse(original_response, url, requestArgs) {
const response = original_response.clone();
if (!response.ok) return original_response;
const ct = response.headers.get('Content-Type')?.toLowerCase() || '';
const convGetMatch = url.match(/\/rest\/app-chat\/conversation\/([a-f0-9-]+)$/i);
if (convGetMatch && requestArgs?.method === 'GET') {
demodInitCache = { headers: new Headers(requestArgs.headers), credentials: requestArgs.credentials || 'include' };
if (!currentConversationId) currentConversationId = convGetMatch[1];
}
if (!currentConversationId) { const id = extractConversationIdFromUrl(url); if (id) currentConversationId = id; }
// ── SSE stream processing ────────────────────────────────
if (ct.includes('text/event-stream')) {
const reader = response.body.getReader();
const stream = new ReadableStream({
async start(controller) {
let buffer = '';
let currentEvent = { data: '', type: 'message', id: null };
try {
while (true) {
const { done, value } = await reader.read();
if (done) {
if (buffer.trim() && (buffer.startsWith('{') || buffer.startsWith('['))) {
try { let j = JSON.parse(buffer); j = await processPotentialModeration(j, 'SSE-Final'); controller.enqueue(_encoder.encode(`data: ${JSON.stringify(j)}\n\n`)); }
catch (_) { controller.enqueue(_encoder.encode(`data: ${buffer}\n\n`)); }
} else if (buffer.trim()) {
controller.enqueue(_encoder.encode(`data: ${buffer}\n\n`));
} else if (currentEvent.data) {
try { let j = JSON.parse(currentEvent.data); j = await processPotentialModeration(j, 'SSE-Event'); controller.enqueue(_encoder.encode(`data: ${JSON.stringify(j)}\n\n`)); }
catch (_) { controller.enqueue(_encoder.encode(`data: ${currentEvent.data}\n\n`)); }
}
controller.close(); break;
}
buffer += _decoder.decode(value, { stream: true });
let lines = buffer.split('\n');
buffer = lines.pop() || '';
for (const line of lines) {
if (line.trim() === '') {
if (currentEvent.data) {
if (currentEvent.data.startsWith('{') || currentEvent.data.startsWith('[')) {
try {
let j = JSON.parse(currentEvent.data);
if (j.conversation_id && !currentConversationId) currentConversationId = j.conversation_id;
j = await processPotentialModeration(j, 'SSE');
controller.enqueue(_encoder.encode(`data: ${JSON.stringify(j)}\n\n`));
} catch (_) { controller.enqueue(_encoder.encode(`data: ${currentEvent.data}\n\n`)); }
} else {
controller.enqueue(_encoder.encode(`data: ${currentEvent.data}\n\n`));
}
}
currentEvent = { data: '', type: 'message', id: null };
} else if (line.startsWith('data:')) {
currentEvent.data += (currentEvent.data ? '\n' : '') + line.substring(5).trim();
} else if (line.startsWith('event:')) {
currentEvent.type = line.substring(6).trim();
} else if (line.startsWith('id:')) {
currentEvent.id = line.substring(3).trim();
}
}
}
} catch (e) { controller.error(e); } finally { reader.releaseLock(); }
}
});
return new Response(stream, { status: response.status, statusText: response.statusText, headers: new Headers(response.headers) });
}
// ── JSON response processing ─────────────────────────────
if (ct.includes('application/json')) {
try {
const text = await response.text();
let json = JSON.parse(text);
if (json.conversation_id && !currentConversationId) currentConversationId = json.conversation_id;
json = await processPotentialModeration(json, 'Fetch');
const body = JSON.stringify(json);
const nh = new Headers(response.headers);
if (nh.has('content-length')) nh.set('content-length', _encoder.encode(body).byteLength.toString());
return new Response(body, { status: response.status, statusText: response.statusText, headers: nh });
} catch (_) { return original_response; }
}
return original_response;
}
// ── Install fetch interceptor ────────────────────────────────
_win.fetch = async function (input, init) {
let url;
let requestArgs = init || {};
const isReqObj = (input instanceof Request);
try { url = isReqObj ? input.url : String(input); }
catch (_) { return _originalFetch.apply(this, arguments); }
// Resolve method: init overrides Request properties
const method = requestArgs.method || (isReqObj ? input.method : undefined);
// ── Custom Response Style: prepend instructions to the user message ──
const isChatPost = method === 'POST' && url.includes('/rest/app-chat/conversation');
if (ge_activeStyleId && isChatPost) {
try {
const styles = ge_getCustomStyles();
const activeStyle = styles.find(s => s.id === ge_activeStyleId);
if (activeStyle) {
let bodyText = null;
if (typeof requestArgs.body === 'string') {
bodyText = requestArgs.body;
} else if (isReqObj) {
const cloned = input.clone();
bodyText = await cloned.text();
}
if (bodyText) {
const json = JSON.parse(bodyText);
// Find the user message field and prepend style instructions
const msgKey = ['message', 'content', 'text', 'prompt'].find(k => typeof json[k] === 'string');
if (msgKey) {
json[msgKey] = '[Follow these response-style instructions for this and all subsequent replies in this conversation: ' + activeStyle.instructions + ']\n\n' + json[msgKey];
}
const newBody = JSON.stringify(json);
if (isReqObj) {
input = new Request(input, { body: newBody });
requestArgs = init || {};
} else {
requestArgs = { ...requestArgs, body: newBody };
}
logDebug('[CustomStyle] Injected "' + activeStyle.name + '" into ' + url);
}
}
} catch (err) {
console.warn('[GrokEnhancer] CustomStyle inject error:', err);
}
}
// ── Imagine Menu: Video length override ──
if (featureImagineMenu && ge_imInterceptOn && isChatPost) {
try {
let bodyText2 = null;
if (typeof requestArgs.body === 'string') {
bodyText2 = requestArgs.body;
} else if (isReqObj) {
const cloned2 = input.clone();
bodyText2 = await cloned2.text();
}
if (bodyText2) {
const json2 = JSON.parse(bodyText2);
const hasVideoGen = json2.toolOverrides?.videoGen !== undefined;
const hasVideoConfig = json2.responseMetadata?.modelConfigOverride?.modelMap?.videoGenModelConfig;
if (hasVideoGen || hasVideoConfig) {
if (!json2.responseMetadata) json2.responseMetadata = {};
if (!json2.responseMetadata.modelConfigOverride) json2.responseMetadata.modelConfigOverride = {};
if (!json2.responseMetadata.modelConfigOverride.modelMap) json2.responseMetadata.modelConfigOverride.modelMap = {};
if (!json2.responseMetadata.modelConfigOverride.modelMap.videoGenModelConfig)
json2.responseMetadata.modelConfigOverride.modelMap.videoGenModelConfig = {};
const cfg = json2.responseMetadata.modelConfigOverride.modelMap.videoGenModelConfig;
const oldLen = cfg.videoLength;
cfg.videoLength = ge_imVideoLength;
ge_imInterceptCount++;
logDebug(`[ImagineMenu] Video length ${oldLen || 'default'} → ${ge_imVideoLength} (#${ge_imInterceptCount})`);
const newBody2 = JSON.stringify(json2);
if (isReqObj) { input = new Request(input, { body: newBody2 }); requestArgs = init || {}; }
else { requestArgs = { ...requestArgs, body: newBody2 }; }
ge_updateImStatus();
}
// ── Imagine Menu: Prompt injection ──
if (ge_imActivePromptId) {
const prompts = ge_getPrompts();
const ap = prompts.find(p => p.id === ge_imActivePromptId);
if (ap && ap.text) {
const msgK = ['message', 'content', 'text', 'prompt'].find(k => typeof json2[k] === 'string');
if (msgK && !json2[msgK].includes(ap.text)) {
json2[msgK] = ap.text + '\n\n' + json2[msgK];
logDebug('[ImagineMenu] Injected prompt:', ap.name);
}
// Clear active prompt if not auto-retry
if (!ge_imAutoRetry) {
ge_imActivePromptId = null;
setState('GrokEnhancer_ActivePromptId', null);
ge_updateImActiveLabel();
}
const nb = JSON.stringify(json2);
if (isReqObj) { input = new Request(input, { body: nb }); requestArgs = init || {}; }
else { requestArgs = { ...requestArgs, body: nb }; }
}
}
}
} catch (err2) {
console.warn('[GrokEnhancer] ImagineMenu intercept error:', err2);
}
}
// ── Media API intercept for downloader database ──
if (url.includes('/rest/media/post/list')) {
const resp = await _originalFetch.call(this, input, requestArgs);
try {
const clone = resp.clone();
const data = await clone.json();
ge_processApiMedia(data);
} catch (e) { logError('[Downloader] API intercept error:', e); }
return resp;
}
// ── Sniff file DELETE endpoint so ge_deleteFile can reuse the real URL ──
if (method === 'DELETE' && (url.includes('/file') || url.includes('/files'))) {
try {
// Store a template with <id> replacing the last path segment (the file ID)
const parts = url.split('/');
const lastSeg = parts[parts.length - 1];
if (/^[0-9a-f-]{20,}$/i.test(lastSeg) || /^\d+$/.test(lastSeg)) {
parts[parts.length - 1] = '<id>';
_ge_sniffedDeleteEndpoint = parts.join('/');
logDebug('[Deleter] sniffed DELETE template:', _ge_sniffedDeleteEndpoint);
}
} catch (_) {}
}
if (!featureDeMod) return _originalFetch.call(this, input, requestArgs);
if (!url.includes('/rest/app-chat/')) return _originalFetch.call(this, input, requestArgs);
if (method === 'POST') {
const id = extractConversationIdFromUrl(url);
if (id) {
if (!currentConversationId) currentConversationId = id;
const hdrs = requestArgs.headers || (isReqObj ? input.headers : null);
if (!demodInitCache && hdrs) demodInitCache = { headers: new Headers(hdrs), credentials: requestArgs.credentials || (isReqObj ? input.credentials : 'include') };
}
return _originalFetch.call(this, input, requestArgs);
}
try {
const resp = await _originalFetch.call(this, input, requestArgs);
return await handleFetchResponse(resp, url, requestArgs);
} catch (e) { throw e; }
};
// ── Install WebSocket interceptor ────────────────────────────
_win.WebSocket = new Proxy(_OriginalWebSocket, {
construct(target, args) {
const ws = new target(...args);
let originalOnMessageHandler = null;
Object.defineProperty(ws, 'onmessage', {
configurable: true, enumerable: true,
get() { return originalOnMessageHandler; },
async set(handler) {
originalOnMessageHandler = handler;
ws.onmessageinternal = async function (event) {
if (!featureDeMod || typeof event.data !== 'string' || !event.data.startsWith('{')) {
if (originalOnMessageHandler) try { originalOnMessageHandler.call(ws, event); } catch (_) { }
return;
}
try {
let json = JSON.parse(event.data);
if (json.conversation_id && json.conversation_id !== currentConversationId) currentConversationId = json.conversation_id;
const processed = await processPotentialModeration(json, 'WebSocket');
const ne = new MessageEvent('message', { data: JSON.stringify(processed), origin: event.origin, lastEventId: event.lastEventId, source: event.source, ports: event.ports });
if (originalOnMessageHandler) try { originalOnMessageHandler.call(ws, ne); } catch (_) { }
} catch (_) {
if (originalOnMessageHandler) try { originalOnMessageHandler.call(ws, event); } catch (_2) { }
}
};
ws.addEventListener('message', ws.onmessageinternal);
}
});
const wrapHandler = (eventName) => {
let oh = null;
Object.defineProperty(ws, `on${eventName}`, {
configurable: true, enumerable: true,
get() { return oh; },
set(handler) {
oh = handler;
ws.addEventListener(eventName, (e) => {
if (eventName === 'message') return;
if (oh) try { oh.call(ws, e); } catch (_) { }
});
}
});
};
wrapHandler('close');
wrapHandler('error');
return ws;
}
});
// ══════════════════════════════════════════════════════════════
// 3c. Hide Satisfaction / Feedback Popups
// ══════════════════════════════════════════════════════════════
const POPUP_STYLE_ID = 'ge-hide-popups-css';
function ge_applyPopupHideCSS(on) {
let style = document.getElementById(POPUP_STYLE_ID);
if (on) {
if (!style) {
style = document.createElement('style');
style.id = POPUP_STYLE_ID;
document.head.appendChild(style);
}
// Target the satisfaction popup, Think Harder suggestion, Connect X banner, and notification toast
style.textContent = `
/* Satisfaction / feedback popup */
div.rounded-3xl.backdrop-blur-lg.border.bg-input {
display: none !important;
}
/* Think Harder suggestion */
div.relative.pt-2:has(> button.pe-7) {
display: none !important;
}
/* Connect X account banner */
div.group:has(> div > .text-2xl):has(button[aria-label="Close"]) {
display: none !important;
}
div.rounded-2xl.border.bg-surface-base.shadow-sm:has(> div > div > .text-2xl) {
display: none !important;
}
/* "Add X account" / "Connect your X account" modal overlay */
div.fixed.inset-0:has([role="dialog"]):has(img[src*="x.com"]),
div.fixed.inset-0:has([role="dialog"]):has(svg[viewBox="0 0 24 24"]):has(button) {
display: none !important;
}
/* "Get notified when Grok finishes" notification toast */
li[data-sonner-toast].toast.bg-popover:has(svg.lucide-bell-ring) {
display: none !important;
}
ol[data-sonner-toaster]:has(li[data-sonner-toast] svg.lucide-bell-ring) {
display: none !important;
}
/* Quick Answer suggestion button */
div.relative.pt-2:has(> button.pe-7:has(svg path[d*="4 14.5L14.2857 2"])) {
display: none !important;
}
button.pe-7:has(svg path[d*="4 14.5L14.2857 2"]) {
display: none !important;
}
/* Imagine button with sparkle decoration — hide entire container */
span:has([data-sparkle-wrapper]) {
display: none !important;
}
`;
} else if (style) {
style.remove();
}
}
// ══════════════════════════════════════════════════════════════
// 3c2. Hide Premium Stuff (SuperGrok upsell banners, sidebar, header, model menu)
// ══════════════════════════════════════════════════════════════
const PREMIUM_STYLE_ID = 'ge-hide-premium-css';
function ge_applyPremiumHideCSS(on) {
let style = document.getElementById(PREMIUM_STYLE_ID);
if (on) {
if (!style) {
style = document.createElement('style');
style.id = PREMIUM_STYLE_ID;
document.head.appendChild(style);
}
style.textContent = `
/* SuperGrok upsell — small fixed bottom-right banner */
div.upsell-small {
display: none !important;
}
/* SuperGrok upsell — inline wider banner with gradient */
div[role="button"].rounded-3xl.bg-black.text-white.dark:has(button:is([aria-label="Hide upsell banner"], :has(> span > svg))) {
display: none !important;
}
/* SuperGrok / Get SuperGrok sidebar row — matched by logo SVG + Upgrade button */
div.flex.items-center.justify-between:has(svg[viewBox="0 0 149 33"]):has(button[aria-label="Upgrade"]),
div.flex.items-center.justify-between:has(svg[viewBox="0 0 149 33"]):has(a[href*="premium"]) {
display: none !important;
}
/* "Try Free" / Upgrade header button — use attribute selector (avoids sm:block escaping) */
div[class~="hidden"][class~="sm:block"]:has(> button:has(svg[viewBox="0 0 35 33"])),
div.hidden.sm\\:block:has(> button:has(svg[viewBox="0 0 35 33"])) {
display: none !important;
}
/* SuperGrok upsell in model mode dropdown */
[role="menuitem"][class*="model-mode-select-upsell"],
[role="menuitem"][class*="upsell"] {
display: none !important;
}
/* SuperGrok upsell menuitem with SuperGrok SVG logo */
[role="menuitem"].rounded-2xl.border-2:has(svg[viewBox="0 0 248 65"]) {
display: none !important;
}
[role="menuitem"].rounded-2xl.border-2:has(svg[viewBox="0 0 92 18"]) {
display: none !important;
}
/* "Upgrade plan" menuitem in context/hamburger menus */
[role="menuitem"]:has(svg[viewBox="0 0 35 33"]) {
display: none !important;
}
`;
} else if (style) {
style.remove();
}
}
function ge_dismissPremium() {
if (!featureHidePremium) return;
// SuperGrok upsell banners — hide inline (never remove React-managed nodes)
document.querySelectorAll('div.upsell-small, div[role="button"].rounded-3xl.bg-black.text-white').forEach(el => {
if (/supergrok|unlock|try free|fewer rate limits/i.test(el.textContent)) {
el.style.setProperty('display', 'none', 'important');
logDebug('[HidePremium] Hidden SuperGrok upsell');
}
});
// SuperGrok upsell in model menu — hide inline so Radix/React can still reconcile
document.querySelectorAll('[role="menuitem"]').forEach(el => {
if ((el.className.includes('model-mode-select-upsell') || /upsell/i.test(el.className)) ||
(el.querySelector('svg[viewBox="0 0 248 65"]') || el.querySelector('svg[viewBox="0 0 92 18"]')) ||
(el.querySelector('svg[viewBox="0 0 35 33"]') && /upgrade plan/i.test(el.textContent))) {
el.style.setProperty('display', 'none', 'important');
logDebug('[HidePremium] Hidden model menu upsell');
}
});
// Upgrade button in header (div.hidden.sm:block with button containing 35x33 svg)
document.querySelectorAll('div[class~="sm:block"]').forEach(el => {
if (!el.classList.contains('hidden')) return;
const btn = el.querySelector(':scope > button');
if (btn && /Upgrade/i.test(btn.textContent) && btn.querySelector('svg[viewBox="0 0 35 33"]')) {
el.style.setProperty('display', 'none', 'important');
logDebug('[HidePremium] Hidden Upgrade header button');
}
});
}
// Also dismiss/remove popups via observer for robustness
function ge_dismissPopups() {
if (!featureHidePopups) return;
// Satisfaction / feedback popups
const popups = document.querySelectorAll('div.rounded-3xl.backdrop-blur-lg');
popups.forEach(popup => {
const text = popup.textContent || '';
if (/are you (happy|satisfied)|how was this response/i.test(text)) {
const closeBtn = popup.querySelector('button[aria-label="Close"]');
if (closeBtn) {
closeBtn.click();
logDebug('[HidePopups] Auto-dismissed satisfaction popup');
}
}
});
// "Think Harder" suggestion buttons
document.querySelectorAll('div.relative.pt-2 > button').forEach(btn => {
if (/think\s*harder/i.test(btn.textContent)) {
const parent = btn.parentElement;
if (parent) {
const closeBtn = parent.querySelector('button[aria-label="Close"]');
if (closeBtn) { closeBtn.click(); logDebug('[HidePopups] Auto-dismissed Think Harder'); }
else parent.style.display = 'none';
}
}
});
// Connect X account banner — hide inline (never remove React-managed nodes)
document.querySelectorAll('div.rounded-2xl.border.bg-surface-base.shadow-sm, div.group.rounded-2xl.border.bg-surface-base').forEach(el => {
if (/connect your.*account/i.test(el.textContent)) {
el.style.setProperty('display', 'none', 'important');
logDebug('[HidePopups] Hidden Connect X banner');
}
});
// Homepage premium/add-X-account modal overlays that break the page
document.querySelectorAll('div.fixed.inset-0').forEach(el => {
if (/premium|supergrok|add.*x.*account|connect.*x.*account/i.test(el.textContent)) {
const dialog = el.querySelector('[role="dialog"]');
if (dialog) {
el.style.setProperty('display', 'none', 'important');
logDebug('[HidePopups] Hidden homepage modal overlay');
}
}
});
}
// ══════════════════════════════════════════════════════════════
// 3d. Hide Heavy Model from Model Dropdown
// ══════════════════════════════════════════════════════════════
const HEAVY_HIDE_CSS_ID = 'ge-hide-heavy-css';
function ge_applyHideHeavyCSS(on) {
let el = document.getElementById(HEAVY_HIDE_CSS_ID);
if (on) {