您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Automatically unfollow users who do not follow you back on Instagram with UI control and progress bar.
// ==UserScript== // @name Auto Unfollow Instagram // @namespace https://kohardsi.my.id // @version 3.1.0 // @description Automatically unfollow users who do not follow you back on Instagram with UI control and progress bar. // @author Teja Sukmana // @match https://www.instagram.com/* // @icon https://kohardsi.my.id/wp-content/uploads/2024/12/logo-kohar.png // @grant GM_addStyle // @grant GM_setValue // @grant GM_getValue // @grant GM_registerMenuCommand // @grant GM_notification // @grant GM_openInTab // @license MIT // @run-at document-idle // @homepage https://kohardsi.my.id // @supportURL https://kohardsi.my.id // ==/UserScript== (async () => { /* ========== HELPER ========== */ const sleep = ms => new Promise(r => setTimeout(r, ms)); const randomDelay = (base = 7000, variation = 3000) => base + Math.floor(Math.random() * variation); const getCookie = name => { const v = `; ${document.cookie}`.split(`; ${name}=`); return v.length === 2 ? v.pop().split(';').shift() : null; }; /* akun yang tidak akan di‑unfollow */ const SKIP_USERS = ['kohardsi', 'instagram', 'meta', 'verified']; const SKIP_USERS_LOWER = SKIP_USERS.map(u => u.toLowerCase()); /* ========== UI ========== */ const ui = document.createElement('div'); ui.innerHTML = ` <style> #unfollowPanel h3{ text-align:center;font-size:20px;padding:0 0 10px;} #unfollowFloatingBtn{position:fixed;bottom:20px;right:20px;z-index:999999;background:#1e1e1e;color:#fff;border:none;border-radius:50%;width:50px;height:50px;font-size:24px;cursor:pointer;box-shadow:0 0 10px rgba(0,0,0,.3);} #unfollowPanel{position:fixed;bottom:80px;right:20px;background:#121212;color:#fff;border-radius:12px;padding:20px;max-width:300px;font-family:sans-serif;box-shadow:0 0 20px rgba(0,0,0,.4);z-index:999999;display:none;} #unfollowPanel input{width:100%;padding:6px;margin:5px 0 10px;background:#2c2c2c;color:#fff;border:1px solid #444;border-radius:6px;} #unfollowPanel button{width:100%;padding:8px;margin-top:10px;border:none;border-radius:6px;cursor:pointer;} #unfollowStartBtn{background:green;color:#fff;} #unfollowPauseBtn{background:#555;color:#fff;} #unfollowCloseBtn{background:crimson;color:#fff;} #unfollowList{margin-top:10px;max-height:100px;overflow-y:auto;font-size:12px;} #unfollowProgress{background:#444;border-radius:4px;height:10px;overflow:hidden;margin:10px 0;} #unfollowProgressBar{height:100%;background:limegreen;width:0%;transition:width .3s;} .rate-limit-warning{color:orange;font-size:12px;margin-top:10px;display:none;} </style> <button id="unfollowFloatingBtn">⚙️</button> <div id="unfollowPanel"> <h3>Auto Unfollow</h3> <div class="rate-limit-warning" id="rateLimitWarning">⚠️ Jeda 15 menit karena batasan Instagram</div> <label>Limit Unfollow:</label><input type="number" id="unfollowLimit" value="10" min="1"/> <label>Jeda per akun (ms):</label><input type="number" id="unfollowDelay" value="7000" min="3000"/> <button id="unfollowStartBtn">Mulai</button> <button id="unfollowPauseBtn">Pause</button> <button id="unfollowCloseBtn">Tutup</button> <div id="unfollowProgress"><div id="unfollowProgressBar"></div></div> <div id="unfollowCount">Total di‑unfollow: 0</div> <div id="unfollowList"></div> <div style="margin-top:10px;font-size:10px;">© <a href="https://instagram.com/kohardsi" target="_blank" style="color:#999;">kohardsi</a></div> </div>`; document.body.appendChild(ui); /* ========== UI refs ========== */ const btnToggle = document.getElementById('unfollowFloatingBtn'); const panel = document.getElementById('unfollowPanel'); const startBtn = document.getElementById('unfollowStartBtn'); const pauseBtn = document.getElementById('unfollowPauseBtn'); const closeBtn = document.getElementById('unfollowCloseBtn'); const unfollowListDiv = document.getElementById('unfollowList'); const unfollowCountEl = document.getElementById('unfollowCount'); const progressBar = document.getElementById('unfollowProgressBar'); const rateLimitWarning = document.getElementById('rateLimitWarning'); let paused = false, totalToUnfollow = 0, unfollowedCount = 0; btnToggle.onclick = () => panel.style.display = panel.style.display === 'none' ? 'block' : 'none'; closeBtn.onclick = () => panel.style.display = 'none'; pauseBtn.onclick = () => { paused = !paused; pauseBtn.textContent = paused ? 'Lanjutkan' : 'Jeda'; pauseBtn.style.background = paused ? 'orange' : '#555'; }; const updateProgress = () => { progressBar.style.width = totalToUnfollow ? (unfollowedCount / totalToUnfollow * 100) + '%' : '0%'; unfollowCountEl.textContent = `Total di‑unfollow: ${unfollowedCount}/${totalToUnfollow}`; }; const updateList = (u, fail=false) => { const d = document.createElement('div'); d.textContent = `${fail ? '❌ Gagal' : '✅ Berhasil'}: ${u}`; d.style.color = fail ? 'orangered' : 'lightgreen'; unfollowListDiv.appendChild(d); unfollowListDiv.scrollTop = unfollowListDiv.scrollHeight; }; /* ---- Unfollow satu akun ---- */ const unfollowUser = async (userId, username, attempt = 1) => { const csrftoken = getCookie('csrftoken'); if (!csrftoken) { updateList(username, true); return; } try { const response = await fetch(`https://www.instagram.com/web/friendships/${userId}/unfollow/`, { method:'POST', headers:{ 'X-CSRFToken':csrftoken,'Accept':'*/*', 'Content-Type':'application/x-www-form-urlencoded', 'X-Requested-With':'XMLHttpRequest','Referer':location.href, 'User-Agent':navigator.userAgent },credentials:'include' }); /* === PATCH 1: deteksi HTTP 429 === */ if (response.status === 429) throw new Error('rate limited'); const data = await response.json(); if (data.status === 'ok') { unfollowedCount++; updateList(username); updateProgress(); } else throw new Error(data.message||'Unknown'); } catch (e) { console.error(`Unfollow ${username} error:`, e.message); if (e.message.includes('rate limited') || attempt >= 3) { rateLimitWarning.style.display = 'block'; paused = true; pauseBtn.textContent = 'Lanjutkan'; pauseBtn.style.background = 'orange'; setTimeout(()=>{ paused=false;rateLimitWarning.style.display='none'; pauseBtn.textContent='Jeda';pauseBtn.style.background='#555'; },900000); updateList(username,true); return; } await sleep(2000); return unfollowUser(userId, username, attempt+1); } await sleep(randomDelay(+document.getElementById('unfollowDelay').value||7000)); }; /* ---- cek hubungan detail ---- */ /* ======================================================= HYBRID FRIENDSHIP CHECKER ======================================================= */ /* — a. GraphQL web (prioritas) — */ const queryFriendHash = '3cabfbb5a7206eb0a60c5f93d3b1d6b'; // relationship hash async function checkFriendshipGraphQL(id) { try { const vars = { user_id: id }; const r = await fetch( `https://www.instagram.com/graphql/query/?query_hash=${queryFriendHash}&variables=${encodeURIComponent(JSON.stringify(vars))}`, { headers: { 'X-CSRFToken': getCookie('csrftoken'), 'Accept': '*/*', 'X-Requested-With': 'XMLHttpRequest', 'Referer': location.href, 'User-Agent': navigator.userAgent }, credentials: 'include' } ); if (!r.ok) throw new Error(r.status); const d = await r.json(); return { followed_by: d.data.user.is_followed_by_viewer, following : d.data.user.follows_viewer }; } catch (err) { console.warn('GraphQL friendship error', err); return null; } } /* — b. Mobile‑API fallback — */ async function checkFriendshipMobile(id) { try { const r = await fetch( `https://www.instagram.com/api/v1/friendships/show/${id}/`, { headers: { 'X-CSRFToken': getCookie('csrftoken'), 'X-IG-App-ID': '936619743392459', 'X-IG-WWW-Claim': '0', 'Accept': '*/*', 'X-Requested-With': 'XMLHttpRequest', 'Referer': location.href, 'User-Agent': navigator.userAgent }, credentials: 'include' } ); if (!r.ok) throw new Error(r.status); const d = await r.json(); return { followed_by: d.followed_by, following: d.following }; } catch (err) { console.warn('Mobile friendship error', err); return null; } } /* — c. Wrapper yang memilih otomatis — */ let currentChecker = checkFriendshipGraphQL; // mulai dengan GraphQL async function safeCheckFriendship(id) { let rel = await currentChecker(id); if (rel === null && currentChecker === checkFriendshipGraphQL) { console.warn('⌛ GraphQL gagal – switch ke Mobile API'); currentChecker = checkFriendshipMobile; // ganti sekali untuk sesi ini rel = await currentChecker(id); } return rel; } /* ---- Tombol MULAI ---- */ startBtn.onclick = async () => { paused = false; unfollowedCount = 0; unfollowListDiv.innerHTML = ''; rateLimitWarning.style.display = 'none'; updateProgress(); unfollowCountEl.textContent = '🔄 Mengumpulkan data … harap tunggu'; const limit = parseInt(document.getElementById("unfollowLimit").value) || 10; const ds_user_id = getCookie('ds_user_id'); if(!ds_user_id){alert('❌ Login ulang! ds_user_id hilang');return;} const queryHash = 'c56ee0ae1f89cdbd1c89e2bc6b8f3d18'; let hasNextPage = true, endCursor=null, toUnfollow=[]; while(hasNextPage && toUnfollow.length<limit){ const variables={id:ds_user_id,include_reel:true,fetch_mutual:false,first:50,after:endCursor}; try{ const r = await fetch(`https://www.instagram.com/graphql/query/?query_hash=${queryHash}&variables=${encodeURIComponent(JSON.stringify(variables))}`,{ headers:{'X-CSRFToken':getCookie('csrftoken'),'Accept':'*/*','X-Requested-With':'XMLHttpRequest','Referer':location.href,'User-Agent':navigator.userAgent}, credentials:'include' }); const data=await r.json(), edges=data.data.user.edge_follow.edges; for(const edge of edges){ const user=edge.node, uname=user.username.toLowerCase(); if(SKIP_USERS_LOWER.includes(uname)||user.is_verified) continue; /* kasus 1: flag lengkap */ if(user.followed_by_viewer===true && user.follows_viewer===false){ toUnfollow.push({id:user.id,username:user.username}); } /* kasus 2: flag null → cek API detail */ else if(user.followed_by_viewer===true && user.follows_viewer==null){ const fr = await safeCheckFriendship(user.id); if(fr && fr.following && !fr.followed_by){ toUnfollow.push({id:user.id,username:user.username}); } else if(!fr){ updateList(user.username,true); /* === PATCH 2 === */ } await sleep(1000); } if(toUnfollow.length>=limit) break; } hasNextPage=data.data.user.edge_follow.page_info.has_next_page; endCursor=data.data.user.edge_follow.page_info.end_cursor; await sleep(2000); }catch(e){console.error('Get following error:',e);break;} } totalToUnfollow=Math.min(toUnfollow.length,limit); updateProgress(); for(const u of toUnfollow.slice(0,limit)){ while(paused) await sleep(1000); await unfollowUser(u.id,u.username); } alert(`✅ Selesai! Berhasil unfollow: ${unfollowedCount}/${totalToUnfollow} akun`); }; })();