您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Unfriend all Roblox friends with option to recover – Updated to use Roblox Friends API
// ==UserScript== // @name Roblox Mass Unfriend with Recovery (Fixed v3.1) // @namespace http://tampermonkey.net/ // @version 3.1 // @description Unfriend all Roblox friends with option to recover – Updated to use Roblox Friends API // @license MIT // @author TheBestChillieDog // @match https://www.roblox.com/users/*/friends* // @match https://www.roblox.com/my/account#!/friends // @match https://www.roblox.com/users/friends // @match https://www.roblox.com/home/friends* // @grant GM_xmlhttpRequest // @grant GM_notification // @grant GM_setValue // @grant GM_getValue // @grant GM_addStyle // @connect roblox.com // @connect friends.roblox.com // @connect users.roblox.com // @require https://code.jquery.com/jquery-3.6.0.min.js // ==/UserScript== (function() { 'use strict'; // // ─── CONFIGURATION ──────────────────────────────────────────────────────────── // const DELAY_BETWEEN_REQUESTS = 1500; // 1.5s delay between each unfriend/friend request const BACKUP_KEY = 'roblox_friends_backup_v3'; const MAX_RETRIES = 5; // for API calls const LOAD_WAIT_TIME = 1000; // (not actually used any more for DOM scanning) GM_addStyle(` .unfriend-controls { background: #2a2a2a; border: 1px solid #444; border-radius: 6px; padding: 20px; margin: 20px 0; box-shadow: 0 2px 10px rgba(0,0,0,0.3); color: #fff; position: relative; z-index: 10000; } .unfriend-controls h3 { margin-top: 0; color: #fff; border-bottom: 1px solid #444; padding-bottom: 10px; } .unfriend-btn, .recover-btn, .scan-btn, .debug-btn { color: white; border: none; border-radius: 4px; cursor: pointer; font-weight: bold; transition: all 0.3s; } .unfriend-btn { background-color: #e74c3c; padding: 12px 20px; margin-right: 12px; margin-bottom: 10px; font-size: 14px; } .unfriend-btn:hover:not(:disabled) { background-color: #c0392b; transform: translateY(-2px); } .unfriend-btn:disabled { background-color: #666; cursor: not-allowed; transform: none; } .recover-btn { background-color: #2ecc71; padding: 12px 20px; margin-bottom: 10px; font-size: 14px; } .recover-btn:hover:not(:disabled) { background-color: #27ae60; transform: translateY(-2px); } .recover-btn:disabled { background-color: #666; cursor: not-allowed; transform: none; } .scan-btn { background-color: #f39c12; padding: 10px 16px; font-size: 12px; margin-right: 10px; } .scan-btn:hover:not(:disabled) { background-color: #d35400; transform: translateY(-1px); } .scan-btn:disabled { background-color: #666; cursor: not-allowed; transform: none; } .debug-btn { background-color: #3498db; padding: 8px 16px; font-size: 12px; margin-left: 10px; } .debug-btn:hover:not(:disabled) { background-color: #2980b9; transform: translateY(-1px); } .debug-btn:disabled { background-color: #666; cursor: not-allowed; transform: none; } .progress-container { margin-top: 20px; display: none; background: #333; padding: 15px; border-radius: 4px; } .progress-bar { height: 20px; background: linear-gradient(90deg, #3498db, #2ecc71); border-radius: 4px; width: 0%; transition: width 0.5s; margin-bottom: 10px; } .status-text { margin-top: 5px; font-size: 14px; color: #bbb; } .friend-selection { max-height: 400px; overflow-y: auto; border: 1px solid #444; padding: 15px; margin-top: 20px; display: none; background: #333; border-radius: 4px; } .friend-selection h4 { margin-top: 0; color: #fff; } .friend-item { display: flex; align-items: center; padding: 10px 0; border-bottom: 1px solid #444; } .friend-item:last-child { border-bottom: none; } .friend-checkbox { margin-right: 15px; transform: scale(1.2); } .friend-username { flex-grow: 1; color: #fff; } .select-all-container { margin-bottom: 15px; } .warning-message { color: #e74c3c; font-weight: bold; margin: 15px 0; } .debug-info { background: #1a1a1a; border: 1px solid #333; padding: 10px; margin: 10px 0; border-radius: 4px; font-family: monospace; font-size: 12px; color: #ccc; max-height: 300px; overflow-y: auto; display: none; white-space: pre-wrap; } `); // // ─── GLOBAL UI SETUP ───────────────────────────────────────────────────────────── // const controlsDiv = document.createElement('div'); controlsDiv.className = 'unfriend-controls'; controlsDiv.innerHTML = ` <h3> Roblox Mass Unfriend Tool <button class="scan-btn" id="scanBtn">Scan Friends</button> <button class="debug-btn" id="debugBtn">Debug Info</button> </h3> <div class="warning-message"> ⚠️ WARNING: This will permanently remove friends. A backup will be saved for recovery. </div> <button class="unfriend-btn" id="unfriendAllBtn">Unfriend All Friends</button> <button class="recover-btn" id="recoverFriendsBtn">Recover Friends</button> <div class="debug-info" id="debugInfo"></div> <div class="progress-container" id="progressContainer"> <div class="progress-bar" id="progressBar"></div> <div class="status-text" id="statusText"></div> </div> <div class="friend-selection" id="friendSelection"> <h4>Select Friends to Recover</h4> <div class="select-all-container"> <input type="checkbox" id="selectAllFriends" checked> <label for="selectAllFriends">Select All Friends</label> </div> <div id="friendsList"></div> <button class="recover-btn" id="confirmRecoverBtn" style="margin-top: 15px;">Confirm Recovery</button> </div> `; // Try to insert control panel into a likely container function insertControlPanel() { const containers = [ document.querySelector('main'), document.querySelector('.content'), document.querySelector('.container-main'), document.querySelector('#content'), document.querySelector('.page-content'), document.querySelector('body') ].filter(el => el !== null); if (containers.length > 0) { containers[0].insertBefore(controlsDiv, containers[0].firstChild); return true; } return false; } let insertAttempts = 0; const insertInterval = setInterval(() => { if (insertControlPanel() || insertAttempts++ > 15) { clearInterval(insertInterval); } }, 500); function getUIElements() { return { unfriendAllBtn: document.getElementById('unfriendAllBtn'), recoverFriendsBtn: document.getElementById('recoverFriendsBtn'), progressContainer: document.getElementById('progressContainer'), progressBar: document.getElementById('progressBar'), statusText: document.getElementById('statusText'), friendSelection: document.getElementById('friendSelection'), friendsList: document.getElementById('friendsList'), confirmRecoverBtn: document.getElementById('confirmRecoverBtn'), selectAllCheckbox: document.getElementById('selectAllFriends'), debugBtn: document.getElementById('debugBtn'), debugInfo: document.getElementById('debugInfo'), scanBtn: document.getElementById('scanBtn') }; } // // ─── DEBUG INFO ─────────────────────────────────────────────────────────────────── // function showDebugInfo() { const ui = getUIElements(); const debugInfo = ui.debugInfo; if (!debugInfo) return; const isVisible = debugInfo.style.display !== 'block'; debugInfo.style.display = isVisible ? 'block' : 'none'; if (isVisible) { let debugText = 'DOM Analysis:\n\n'; const friendSelectors = [ 'a[href*="/users/"]', '*[class*="friend"]', '*[data-testid*="friend"]', '.avatar', '*[class*="list"]' ]; friendSelectors.forEach(selector => { try { const elements = document.querySelectorAll(selector); debugText += `${selector}: ${elements.length} elements\n`; if (elements.length > 0 && elements.length < 5) { Array.from(elements).forEach((el, i) => { debugText += ` [${i+1}] ${el.tagName} class="${el.className}" href="${el.href || 'N/A'}"\n`; debugText += ` text: "${el.textContent.trim().slice(0, 50)}"\n`; }); } debugText += '\n'; } catch (e) { debugText += `${selector}: ERROR: ${e.message}\n\n`; } }); debugText += `\nPage Info:\n`; debugText += `URL: ${window.location.href}\n`; debugText += `Title: ${document.title}\n`; debugText += `Friends in backup: ${getBackupCount()}\n`; debugInfo.textContent = debugText; } } // // ─── BACKUP UTILITIES ───────────────────────────────────────────────────────────────── // function getBackupCount() { try { const backup = GM_getValue(BACKUP_KEY); return backup ? JSON.parse(backup).length : 0; } catch { return 0; } } // // ─── ROBLOX API UTILITIES ────────────────────────────────────────────────────────────── // // 1) Get the authenticated user’s ID by calling /users/authenticated async function getCurrentUserId() { let retries = 0; while (retries < MAX_RETRIES) { try { const resp = await fetch('https://users.roblox.com/v1/users/authenticated', { method: 'GET', credentials: 'include', headers: { 'Accept': 'application/json' } }); if (!resp.ok) throw new Error(`Status ${resp.status}`); const data = await resp.json(); if (data && data.id) return data.id.toString(); throw new Error('No ID in response'); } catch (e) { retries++; console.warn(`getCurrentUserId attempt ${retries} failed: ${e.message}`); await new Promise(res => setTimeout(res, 1000)); } } throw new Error('Failed to get current user ID after retries'); } // 2) Use Roblox Friends API to fetch ALL friends (paginated) async function getAllFriends() { showNotification('Fetching full friend list...'); try { const userId = await getCurrentUserId(); let allFriends = []; let cursor = ''; do { const url = new URL(`https://friends.roblox.com/v1/users/${userId}/friends`); url.searchParams.set('limit', '100'); if (cursor) url.searchParams.set('cursor', cursor); const resp = await fetch(url.toString(), { method: 'GET', credentials: 'include', headers: { 'Accept': 'application/json' } }); if (!resp.ok) throw new Error(`Status ${resp.status}`); const data = await resp.json(); if (Array.isArray(data.data)) { data.data.forEach(f => { // f is {id: <number>, name: <string>, ...} allFriends.push({ userId: f.id.toString(), username: f.name }); }); } cursor = data.nextPageCursor || ''; } while (cursor); showNotification(`Detected ${allFriends.length} friends.`); return allFriends; } catch (err) { console.error('Error in getAllFriends():', err); showNotification('Failed to load friend list. Are you on a Roblox page while signed in?', true); return []; } } // 3) Get a fresh CSRF token by making a dummy POST request (Roblox standard) async function getCsrfToken() { try { const response = await fetch('https://friends.roblox.com/v1/users/1/request-friendship', { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include', body: JSON.stringify({}) }); const csrfToken = response.headers.get('x-csrf-token'); if (!csrfToken) throw new Error('No X-CSRF-TOKEN in headers'); return csrfToken; } catch (error) { console.error('Error getting CSRF token:', error); showNotification('Could not obtain CSRF token. Refresh the page and try again.', true); throw error; } } // 4) Unfriend a user via API async function unfriendUser(userId, username, csrfToken) { try { const resp = await fetch(`https://friends.roblox.com/v1/users/${userId}/unfriend`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': csrfToken }, credentials: 'include' }); if (resp.ok) { console.log(`✓ Unfriended ${username} (ID: ${userId})`); return true; } else { const errText = await resp.text(); console.error(`✗ Failed to unfriend ${username}: ${resp.status} – ${errText}`); return false; } } catch (e) { console.error(`✗ Error unfriending ${username}:`, e); return false; } } // 5) Send a friend request via API async function friendUser(userId, username, csrfToken) { try { const resp = await fetch(`https://friends.roblox.com/v1/users/${userId}/request-friendship`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': csrfToken }, credentials: 'include', body: JSON.stringify({ friendshipOriginSourceType: 1 }) }); if (resp.ok) { console.log(`✓ Friend request sent to ${username} (ID: ${userId})`); return true; } else { const errText = await resp.text(); console.error(`✗ Failed to send friend request to ${username}: ${resp.status} – ${errText}`); return false; } } catch (e) { console.error(`✗ Error sending request to ${username}:`, e); return false; } } // // ─── NOTIFICATION ─────────────────────────────────────────────────────────────────── // function showNotification(message, isError = false) { console.log(`${isError ? 'ERROR' : 'INFO'}: ${message}`); if (typeof GM_notification !== 'undefined') { GM_notification({ text: message, title: 'Roblox Unfriend Tool', highlight: isError }); } else { alert(message); } } // // ─── MASS UNFRIEND ─────────────────────────────────────────────────────────────────── // async function unfriendAll() { const ui = getUIElements(); if (!ui.unfriendAllBtn) return; const confirmMsg = '⚠️ WARNING: This will unfriend ALL your friends!\n\n' + 'A backup will be saved so you can recover them later.\n\n' + 'Are you absolutely sure you want to continue?'; if (!confirm(confirmMsg)) { return; } // 1) Fetch every friend via API: const friends = await getAllFriends(); if (friends.length === 0) { return; } // 2) Save a backup (array of {username, userId}) GM_setValue(BACKUP_KEY, JSON.stringify(friends)); console.log(`Backup saved: ${friends.length} entries.`); try { ui.unfriendAllBtn.disabled = true; ui.recoverFriendsBtn.disabled = true; ui.progressContainer.style.display = 'block'; ui.statusText.textContent = 'Preparing to unfriend…'; const csrfToken = await getCsrfToken(); let successCount = 0; let failedCount = 0; for (let i = 0; i < friends.length; i++) { const { userId, username } = friends[i]; const pct = Math.round(((i + 1) / friends.length) * 100); ui.progressBar.style.width = `${pct}%`; ui.statusText.textContent = `Unfriending ${username} (${i+1}/${friends.length})…`; const ok = await unfriendUser(userId, username, csrfToken); if (ok) { successCount++; } else { failedCount++; } await new Promise(res => setTimeout(res, DELAY_BETWEEN_REQUESTS)); } ui.progressBar.style.width = '100%'; ui.statusText.textContent = `✅ Completed! Unfriended ${successCount}/${friends.length} friends. ` + `${failedCount} failed.`; showNotification( `Process completed! Unfriended ${successCount} out of ${friends.length} friends.` ); } catch (err) { console.error('Error during mass unfriend:', err); ui.statusText.textContent = 'Error occurred. See console.'; showNotification('Error during mass unfriend. Check console.', true); } finally { ui.unfriendAllBtn.disabled = false; ui.recoverFriendsBtn.disabled = false; } } // // ─── RECOVER FRIENDS ───────────────────────────────────────────────────────────────── // async function showRecoverySelection() { const ui = getUIElements(); if (!ui.friendSelection) return; const backupRaw = GM_getValue(BACKUP_KEY); if (!backupRaw) { showNotification('No backup found. Run “Unfriend All” first.', true); return; } let friends; try { friends = JSON.parse(backupRaw); } catch { showNotification('Could not parse backup data.', true); return; } if (!Array.isArray(friends) || friends.length === 0) { showNotification('Backup is empty.', true); return; } ui.friendsList.innerHTML = ''; friends.forEach(f => { const item = document.createElement('div'); item.className = 'friend-item'; item.innerHTML = ` <input type="checkbox" class="friend-checkbox" id="friend-${f.userId}" checked> <label class="friend-username" for="friend-${f.userId}">${f.username}</label> `; ui.friendsList.appendChild(item); }); ui.selectAllCheckbox.checked = true; ui.friendSelection.style.display = 'block'; showNotification(`Loaded ${friends.length} friends from backup.`); } async function recoverSelectedFriends() { const ui = getUIElements(); if (!ui.friendsList) return; const checkedBoxes = ui.friendsList.querySelectorAll('.friend-checkbox:checked'); if (checkedBoxes.length === 0) { showNotification('No friends selected for recovery.', true); return; } if (!confirm(`Send friend requests to ${checkedBoxes.length} selected friends?`)) { return; } let backup; try { backup = JSON.parse(GM_getValue(BACKUP_KEY)); } catch { showNotification('Backup data is corrupted.', true); return; } const selectedIds = Array.from(checkedBoxes).map(cb => cb.id.replace('friend-', '')); const toRecover = backup.filter(f => selectedIds.includes(f.userId)); try { ui.confirmRecoverBtn.disabled = true; ui.progressContainer.style.display = 'block'; ui.statusText.textContent = 'Preparing recovery…'; const csrfToken = await getCsrfToken(); let successCount = 0; let failedCount = 0; for (let i = 0; i < toRecover.length; i++) { const { userId, username } = toRecover[i]; const pct = Math.round(((i + 1) / toRecover.length) * 100); ui.progressBar.style.width = `${pct}%`; ui.statusText.textContent = `Sending request to ${username} (${i+1}/${toRecover.length})…`; const ok = await friendUser(userId, username, csrfToken); if (ok) { successCount++; } else { failedCount++; } await new Promise(res => setTimeout(res, DELAY_BETWEEN_REQUESTS)); } ui.progressBar.style.width = '100%'; ui.statusText.textContent = `✅ Recovery done: sent ${successCount}/${toRecover.length} requests. ` + `${failedCount} failed.`; showNotification( `Recovery finished! Sent ${successCount} out of ${toRecover.length} requests.` ); ui.friendSelection.style.display = 'none'; } catch (err) { console.error('Error during friend recovery:', err); ui.statusText.textContent = 'Error during recovery. See console.'; showNotification('Error during friend recovery. Check console.', true); } finally { ui.confirmRecoverBtn.disabled = false; } } // // ─── OPTIONAL “SCAN FRIENDS” ───────────────────────────────────────────────────────── // // This now simply calls getAllFriends() to check count and notify. async function scanFriends() { const ui = getUIElements(); if (ui.scanBtn) ui.scanBtn.disabled = true; showNotification('Scanning for friends via API…'); const friends = await getAllFriends(); if (ui.scanBtn) ui.scanBtn.disabled = false; if (friends.length > 0) { showNotification(`Scan complete: found ${friends.length} friends.`); } } // // ─── EVENT BINDING ON LOAD ─────────────────────────────────────────────────────────── // setTimeout(() => { const ui = getUIElements(); if (ui.unfriendAllBtn) { ui.unfriendAllBtn.addEventListener('click', unfriendAll); } if (ui.recoverFriendsBtn) { ui.recoverFriendsBtn.addEventListener('click', showRecoverySelection); } if (ui.confirmRecoverBtn) { ui.confirmRecoverBtn.addEventListener('click', recoverSelectedFriends); } if (ui.debugBtn) { ui.debugBtn.addEventListener('click', showDebugInfo); } if (ui.scanBtn) { ui.scanBtn.addEventListener('click', scanFriends); } if (ui.selectAllCheckbox) { ui.selectAllCheckbox.addEventListener('change', function() { const allBoxes = ui.friendsList.querySelectorAll('.friend-checkbox'); allBoxes.forEach(cb => cb.checked = this.checked); }); } console.log('✅ Roblox Mass Unfriend Tool v3.1 loaded.'); showNotification('Roblox Mass Unfriend Tool loaded! Click “Scan Friends” to test.'); }, 1500); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址