-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathTwitter-Mass-Blocker.js
More file actions
325 lines (274 loc) · 12.1 KB
/
Copy pathTwitter-Mass-Blocker.js
File metadata and controls
325 lines (274 loc) · 12.1 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
// ==UserScript==
// @name X/Twitter Mass Blocker (v8.1 - Custom Page Crawler)
// @namespace http://tampermonkey.net/
// @version 8.1
// @description Crawls index.html, page2.html, page3.html... until 404. Syncs & Blocks.
// @author Haolong
// @match https://x.com/*
// @match https://twitter.com/*
// @connect pluto0x0.github.io
// @grant GM_xmlhttpRequest
// @grant GM_registerMenuCommand
// @license MIT
// ==/UserScript==
(function() {
'use strict';
// --- Configuration ---
const BASE_URL = "https://pluto0x0.github.io/X_based_china/";
const BEARER_TOKEN = "Bearer AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs%3D1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA";
const COOLDOWN_TIME = 180000; // 3 minutes pause on 429 error
// --- State ---
let isPaused = false;
let activeThreads = 0;
let successCount = 0;
let todoList = [];
let concurrency = 2;
let existingBlocks = new Set();
let stopSignal = false;
// --- UI Construction ---
function createUI() {
if (document.getElementById("xb-panel")) return;
const panel = document.createElement('div');
panel.id = "xb-panel";
Object.assign(panel.style, {
position: "fixed", bottom: "20px", left: "20px", zIndex: "99999",
background: "rgba(10, 10, 10, 0.98)", color: "#e7e9ea", padding: "16px",
borderRadius: "12px", width: "340px", fontFamily: "-apple-system, BlinkMacSystemFont, sans-serif",
border: "1px solid #333", boxShadow: "0 8px 32px rgba(0,0,0,0.6)"
});
panel.innerHTML = `
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;">
<span style="font-weight:800;color:#f91880;font-size:14px;">Mass Blocker v8.1</span>
<span id="xb-threads-disp" style="font-size:10px;background:#333;padding:2px 6px;borderRadius:4px;">Threads: 2</span>
</div>
<div style="margin-bottom:12px;">
<input type="range" id="xb-speed" min="1" max="5" value="2" style="width:100%;accent-color:#f91880;">
</div>
<div id="xb-log" style="height:130px;overflow-y:auto;background:#000;border:1px solid #333;padding:8px;font-size:11px;color:#888;margin-bottom:12px;border-radius:4px;font-family:monospace;white-space:pre-wrap;">Ready.</div>
<div style="background:#333;height:6px;width:100%;margin-bottom:12px;border-radius:3px;overflow:hidden;">
<div id="xb-bar" style="background:#f91880;height:100%;width:0%;transition:width 0.3s ease;"></div>
</div>
<div style="display:flex;gap:10px;">
<button id="xb-btn" style="flex:1;padding:10px;background:#f91880;color:white;border:none;border-radius:20px;cursor:pointer;font-weight:bold;font-size:13px;">START</button>
<button id="xb-stop" style="flex:0.4;padding:10px;background:#333;color:white;border:none;border-radius:20px;cursor:pointer;font-weight:bold;font-size:13px;">STOP</button>
</div>
`;
document.body.appendChild(panel);
document.getElementById("xb-btn").onclick = runFullProcess;
document.getElementById("xb-stop").onclick = () => { stopSignal = true; log("🛑 Stopping...", "red"); };
document.getElementById("xb-speed").oninput = (e) => {
concurrency = parseInt(e.target.value);
document.getElementById("xb-threads-disp").innerText = `Threads: ${concurrency}`;
};
}
function log(msg, color="#888") {
const el = document.getElementById("xb-log");
const time = new Date().toLocaleTimeString([], {hour12:false});
el.innerHTML = `<div style="color:${color}"><span style="opacity:0.5">[${time}]</span> ${msg}</div>` + el.innerHTML;
}
function updateProgress(done, total) {
if(total < 1) return;
const pct = Math.floor((done / total) * 100);
document.getElementById("xb-bar").style.width = `${pct}%`;
document.getElementById("xb-btn").innerText = `${pct}% (${done})`;
}
// --- Helpers ---
function getCsrfToken() {
const match = document.cookie.match(/(^|;\s*)ct0=([^;]*)/);
return match ? match[2] : null;
}
const sleep = (ms) => new Promise(r => setTimeout(r, ms));
// --- Step 1: Sync Existing Blocks ---
async function fetchExistingBlocks() {
log("🔄 Syncing existing blocks from X...", "#1d9bf0");
let cursor = -1;
existingBlocks.clear();
stopSignal = false;
try {
while (cursor !== 0 && cursor !== "0" && !stopSignal) {
const csrf = getCsrfToken();
if (!csrf) throw new Error("Logged out");
const url = `https://x.com/i/api/1.1/blocks/ids.json?count=5000&cursor=${cursor}&stringify_ids=true`;
const res = await fetch(url, {
headers: {
"authorization": BEARER_TOKEN,
"x-csrf-token": csrf,
"content-type": "application/json"
}
});
if (!res.ok) {
if(res.status === 429) {
log("⚠️ Sync 429. Waiting 30s...", "orange");
await sleep(30000);
continue;
}
throw new Error(`API Error ${res.status}`);
}
const data = await res.json();
if (data.ids) data.ids.forEach(id => existingBlocks.add(String(id)));
cursor = data.next_cursor_str;
await sleep(200);
}
if(stopSignal) return false;
log(`✅ Sync Complete: ${existingBlocks.size} already blocked.`, "#00ba7c");
return true;
} catch (e) {
log(`❌ Sync Error: ${e.message}`, "red");
return false;
}
}
// --- Step 2: Crawl (Corrected URL Pattern) ---
function fetchUrlText(url) {
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: "GET",
url: url,
onload: (res) => {
if (res.status === 200) resolve(res.responseText);
else reject(res.status);
},
onerror: () => reject("Network Error")
});
});
}
async function crawlTargetList() {
let allFoundIds = new Set();
let page = 1;
let keepCrawling = true;
log("🕸️ Starting Crawler...", "#1d9bf0");
while(keepCrawling && !stopSignal) {
// URL Logic: Page 1 = index.html, Page 2 = page2.html
let url = (page === 1)
? `${BASE_URL}index.html`
: `${BASE_URL}page${page}.html`;
document.getElementById("xb-btn").innerText = `Scan Pg ${page}`;
try {
const html = await fetchUrlText(url);
// Extract IDs
const matches = [...html.matchAll(/ID:\s*(\d+)/g)];
const idsOnPage = matches.map(m => m[1]);
if (idsOnPage.length === 0) {
log(`⚠️ Page ${page} loaded but no IDs found.`);
} else {
idsOnPage.forEach(id => allFoundIds.add(id));
}
page++;
await sleep(200); // Gentle crawl delay
} catch (err) {
if (err === 404) {
log(`✅ End of list at Page ${page-1}.`, "#00ba7c");
keepCrawling = false; // Stop on 404
} else {
log(`❌ Crawl Error Pg ${page}: ${err}`, "red");
keepCrawling = false;
}
}
}
return [...allFoundIds];
}
// --- Main Logic ---
async function runFullProcess() {
const btn = document.getElementById("xb-btn");
btn.disabled = true;
stopSignal = false;
// 1. Check Login
if (!getCsrfToken()) {
log("❌ Error: Logged out.", "red");
btn.disabled = false;
return;
}
// 2. Sync Blocks
const syncSuccess = await fetchExistingBlocks();
if (!syncSuccess) {
btn.disabled = false;
btn.innerText = "Retry";
return;
}
// 3. Crawl Pages
const githubIds = await crawlTargetList();
if (!githubIds || githubIds.length === 0) {
log("❌ No IDs found during crawl.", "red");
btn.disabled = false;
return;
}
// 4. Diffing
todoList = githubIds.filter(id => !existingBlocks.has(id));
const total = todoList.length;
const blockedAlready = githubIds.length - total;
log(`📄 Found: ${githubIds.length} total.`);
log(`⏭️ Skipped: ${blockedAlready} (Already blocked).`);
if (total === 0) {
log("🎉 All targets are already blocked!", "#00ba7c");
updateProgress(1,1);
btn.disabled = false;
btn.innerText = "Done";
return;
}
log(`🎯 <b>Queued to Block: ${total}</b>`, "#f91880");
// 5. Start Blocking
startManager(total);
}
async function startManager(totalInitial) {
let processedCount = 0;
while ((todoList.length > 0 || activeThreads > 0) && !stopSignal) {
if (isPaused) { await sleep(1000); continue; }
while (activeThreads < concurrency && todoList.length > 0 && !isPaused && !stopSignal) {
const uid = todoList.shift();
processUser(uid, totalInitial);
}
await sleep(200);
}
document.getElementById("xb-btn").innerText = stopSignal ? "Stopped" : "Finished";
document.getElementById("xb-btn").disabled = false;
if(!stopSignal) log("🏁 Job Finished.", "#00ba7c");
alert("Batch Complete");
}
async function processUser(uid, totalInitial) {
activeThreads++;
try {
await sleep(Math.floor(Math.random() * 500) + 300);
const csrf = getCsrfToken();
if(!csrf) throw new Error("Logout detected");
const res = await fetch("https://x.com/i/api/1.1/blocks/create.json", {
method: "POST",
headers: {
"authorization": BEARER_TOKEN,
"x-csrf-token": csrf,
"content-type": "application/x-www-form-urlencoded",
"x-twitter-active-user": "yes",
"x-twitter-auth-type": "OAuth2Session"
},
body: `user_id=${uid}`
});
if (res.ok || res.status === 200 || res.status === 403 || res.status === 404) {
successCount++;
if (successCount % 5 === 0) log(`Blocked: ${uid}`);
} else if (res.status === 401) {
log(`❌ 401 Session Died. Stopping.`, "red");
stopSignal = true;
} else if (res.status === 429) {
if (!isPaused) {
isPaused = true;
log(`🛑 Rate Limit 429. Pausing 3m...`, "red");
setTimeout(() => {
isPaused = false;
log("🟢 Resuming...", "#00ba7c");
}, COOLDOWN_TIME);
}
todoList.push(uid);
successCount--;
} else {
log(`⚠️ ${res.status} on ${uid}`, "orange");
}
} catch (e) {
log(`❌ Err: ${e.message}`, "red");
if(e.message.includes("Logout")) stopSignal = true;
else todoList.push(uid);
successCount--;
}
activeThreads--;
updateProgress(successCount, totalInitial);
}
setTimeout(createUI, 1500);
GM_registerMenuCommand("Open Blocker", createUI);
})();