View difference between Paste ID: ZaJSV4fc and gG2NNLmw
SHOW: | | - or go back to the newest paste.
1
// ==UserScript==
2
// @name         RG Tweet Video Remover with Catalog
3
// @namespace    http://tampermonkey.net/
4-
// @version      1.1
4+
// @version      2.0
5-
// @description  Floating RG button removes tweet_video posts, catalogs URLs, and copies them via 📋 button
5+
// @description  Remove tweet_video posts, catalog any /status/ URLs (any domain), with copy and collapsible UI
6
// @author       you
7
// @match        *://nitter.privacydev.net/*
8
// @match        *://nitter.poast.org/*
9
// @match        *://xcancel.com/*
10
// @match        *://lightbrd.com/*
11
// @match        *://nitter.net/*
12
// @grant        GM_setClipboard
13
// ==/UserScript==
14
15
(function() {
16
    'use strict';
17
18
    let isActive = false;
19
    let observer = null;
20
    let deletedUrls = new Set();
21
22
    function isDarkMode() {
23
        return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
24
    }
25
26-
    function getStatusUrl(item) {
26+
    function getStatusUrls(item) {
27-
        // Try to find the status link in the tweet/post element
27+
        let urls = [];
28-
        let aList = item.querySelectorAll('a[href*="/status/"]');
28+
        let links = item.querySelectorAll('a[href*="/status/"]');
29-
        for (let a of aList) {
29+
        for (let a of links) {
30-
            let match = a.href.match(/https:\/\/(twitter|x)\.com\/[^/]+\/status\/\d+/);
30+
            // Regex matches any "/status/1234567" style URL
31-
            if (match) return match;
31+
            let match = a.href.match(/.+\/status\/\d+/);
32
            if (match && !urls.includes(match)) {
33-
        return null;
33+
                urls.push(match);
34
            }
35
        }
36
        return urls;
37
    }
38
39
    function removeRelevantPosts() {
40
        const items = document.querySelectorAll('.timeline-item, article, [data-testid="tweet"]');
41
        items.forEach(function(item) {
42
            if (
43-
                let url = getStatusUrl(item);
43+
44-
                if (url && !deletedUrls.has(url)) {
44+
45-
                    deletedUrls.add(url);
45+
46-
                    addUrlToCatalog(url);
46+
                let urls = getStatusUrls(item);
47-
                }
47+
                urls.forEach(url => {
48
                    if (!deletedUrls.has(url)) {
49
                        deletedUrls.add(url);
50
                        addUrlToCatalog(url);
51
                    }
52
                });
53-
    // --- UI Elements ---
53+
54
            }
55
        });
56
    }
57
58
    // RG Toggle Button
59
    let fabButton = document.createElement('button');
60
    fabButton.textContent = "RG ";
61
    fabButton.setAttribute('aria-label', 'Remove GIF posts');
62
    fabButton.title = 'RG Inactive: Click to activate';
63
    fabButton.style.position = 'fixed';
64
    fabButton.style.bottom = '24px';
65
    fabButton.style.left = '116px';
66
    fabButton.style.zIndex = '99999';
67
    fabButton.style.fontSize = '18px';
68
    fabButton.style.padding = '10px 18px';
69
    fabButton.style.borderRadius = '24px';
70
    fabButton.style.border = '1.5px solid #ccc';
71
    fabButton.style.boxShadow = '0 4px 24px rgba(0,0,0,0.11)';
72
    fabButton.style.fontWeight = 'bold';
73
    fabButton.style.cursor = 'pointer';
74
    fabButton.style.transition = 'background 0.2s,color 0.2s';
75
    fabButton.style.background = isDarkMode() ? '#222' : '#fff';
76
    fabButton.style.color = isDarkMode() ? '#fff' : '#005940';
77
    fabButton.style.userSelect = 'none';
78
    document.body.appendChild(fabButton);
79
80
    // Catalog UI (collapsible)
81
    let catalogUI = document.createElement('div');
82
    catalogUI.style.position = 'fixed';
83
    catalogUI.style.left = '116px';
84
    catalogUI.style.bottom = '76px';
85
    catalogUI.style.zIndex = '99999';
86
    catalogUI.style.minWidth = '272px';
87
    catalogUI.style.maxWidth = '330px';
88
    catalogUI.style.maxHeight = '300px';
89
    catalogUI.style.overflowY = 'auto';
90
    catalogUI.style.background = isDarkMode() ? '#232629' : '#fbfbfb';
91
    catalogUI.style.color = isDarkMode() ? '#fff' : '#212121';
92
    catalogUI.style.border = '1.5px solid #ccc';
93
    catalogUI.style.borderRadius = '12px';
94
    catalogUI.style.boxShadow = '0 4px 24px rgba(0,0,0,0.12)';
95
    catalogUI.style.padding = '10px 10px 8px 12px';
96
    catalogUI.style.fontSize = '15px';
97
    catalogUI.style.display = 'flex';
98
    catalogUI.style.flexDirection = 'column';
99
    catalogUI.style.gap = '7px';
100
    catalogUI.style.userSelect = 'text';
101
102
    // Collapsible header
103
    const catalogHeader = document.createElement('div');
104
    catalogHeader.style.display = 'flex';
105
    catalogHeader.style.justifyContent = 'space-between';
106
    catalogHeader.style.alignItems = 'center';
107
    catalogHeader.style.cursor = 'pointer';
108
    catalogHeader.style.paddingBottom = '6px';
109
    catalogHeader.style.borderBottom = '1px solid #ccc';
110
111
    const headerTitle = document.createElement('span');
112
    headerTitle.textContent = '🗂️ Removed Status URLs';
113
114
    const collapseBtn = document.createElement('span');
115
    collapseBtn.textContent = '▾'; // Collapsed: '▸', Expanded: '▾'
116
    collapseBtn.style.fontSize = '14px';
117
    collapseBtn.style.marginLeft = '7px';
118
119
    catalogHeader.appendChild(headerTitle);
120
    catalogHeader.appendChild(collapseBtn);
121
122
    // URL List
123
    const urlList = document.createElement('div');
124
    urlList.id = 'rg-url-list';
125
    urlList.style.margin = '7px 0 0 0';
126
    urlList.style.display = 'block';
127
128
    // Copy button
129
    const copyBtn = document.createElement('button');
130
    copyBtn.textContent = '📋';
131
    copyBtn.title = 'Copy all URLs';
132
    copyBtn.style.margin = '3px 0 0 3px';
133
    copyBtn.style.border = '1px solid #bbb';
134
    copyBtn.style.borderRadius = '6px';
135
    copyBtn.style.padding = '3px 7px';
136
    copyBtn.style.background = isDarkMode() ? '#333' : '#eee';
137
    copyBtn.style.color = isDarkMode() ? '#fff' : '#222';
138
    copyBtn.style.fontSize = '15px';
139
    copyBtn.style.cursor = 'pointer';
140
    copyBtn.style.alignSelf = 'flex-start';
141
    copyBtn.style.transition = 'background 0.15s';
142
143
    copyBtn.addEventListener('click', function () {
144
        const allUrls = Array.from(deletedUrls).join('\n');
145
        if (typeof GM_setClipboard !== 'undefined') {
146
            GM_setClipboard(allUrls, { type: 'text', mimetype: 'text/plain' });
147
        } else if (navigator.clipboard) {
148
            navigator.clipboard.writeText(allUrls);
149
        }
150
        copyBtn.textContent = '✅';
151
        setTimeout(() => (copyBtn.textContent = '📋'), 900);
152
    });
153
154
    // Collapse functionality
155
    let collapsed = false;
156
    catalogHeader.addEventListener('click', function () {
157
        collapsed = !collapsed;
158
        urlList.style.display = collapsed ? 'none' : 'block';
159
        copyBtn.style.display = collapsed ? 'none' : 'inline-block';
160
        collapseBtn.textContent = collapsed ? '▸' : '▾';
161
    });
162
163
    // Add children to catalog UI
164
    catalogUI.appendChild(catalogHeader);
165
    catalogUI.appendChild(copyBtn);
166
    catalogUI.appendChild(urlList);
167
    document.body.appendChild(catalogUI);
168
169
    function addUrlToCatalog(url) {
170
        if (document.getElementById('rg-url-' + btoa(url))) return;
171
        const urlBox = document.createElement('div');
172
        urlBox.textContent = url;
173
        urlBox.style.background = isDarkMode() ? '#181c1f' : '#f2f2f4';
174
        urlBox.style.borderRadius = '4px';
175
        urlBox.style.padding = '4px 7px';
176
        urlBox.style.margin = '2px 0';
177
        urlBox.style.wordBreak = 'break-all';
178
        urlBox.id = 'rg-url-' + btoa(url);
179
        urlList.appendChild(urlBox);
180
    }
181
182
    function refreshCatalogTheme() {
183
        catalogUI.style.background = isDarkMode() ? '#232629' : '#fbfbfb';
184
        catalogUI.style.color = isDarkMode() ? '#fff' : '#212121';
185
        copyBtn.style.background = isDarkMode() ? '#333' : '#eee';
186
        copyBtn.style.color = isDarkMode() ? '#fff' : '#222';
187
        urlList.querySelectorAll('div').forEach(el => {
188
            el.style.background = isDarkMode() ? '#181c1f' : '#f2f2f4';
189
        });
190
        fabButton.style.background = isDarkMode() ? (isActive ? '#048848':'#222') : (isActive ? '#23be78':'#fff');
191
        fabButton.style.color = isDarkMode() ? '#fff' : (isActive ? '#fff' : '#005940');
192
    }
193-
    // The RG button logic
193+
194
    function setButtonActive(active) {
195
        if(active){
196
            removeRelevantPosts();
197
            observer = new MutationObserver(() => {
198
                removeRelevantPosts();
199
            });
200
            observer.observe(document.body, {childList:true, subtree:true});
201
            fabButton.style.background = isDarkMode() ? '#048848' : '#23be78';
202
            fabButton.style.color = '#fff';
203
            fabButton.title = 'RG Active: Hides tweet_video posts';
204
        } else {
205
            if(observer) observer.disconnect();
206
            observer = null;
207
            fabButton.style.background = isDarkMode() ? '#222' : '#fff';
208
            fabButton.style.color = isDarkMode() ? '#fff' : '#005940';
209
            fabButton.title = 'RG Inactive: Click to activate';
210
        }
211
        refreshCatalogTheme();
212
    }
213
214
    fabButton.addEventListener('click', function() {
215
        isActive = !isActive;
216
        setButtonActive(isActive);
217
    });
218
219
    // On page reload
220
    window.addEventListener("pageshow",function(){
221
        setButtonActive(isActive);
222
    });
223
224
    // Update on dark/light change
225
    window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', refreshCatalogTheme);
226
227-
    // Don't let the catalog or button block pointer events to stuff underneath
227+
228
    catalogUI.style.pointerEvents = 'auto';
229
230
    fabButton.id = '__rg_fab_button';
231-
    // Extra: give RG button and catalog unique IDs for styling/debug
231+
232
233
})();