쓰팔 안 한 친구 찾기

스레드에서 내가 팔로우하지만 나를 팔로우하지 않는 계정을 찾아 드려요. 스크립트 하나만 복사해서 붙여 넣으면 돼요.

누가 뒷삭을 하였는가~

계정 정보는 받지 않아요

사용 방법

  1. 1

    스크립트 복사하고 AI로 검사

    이 도구는 threads.com 에 로그인한 탭의 개발자 도구에 스크립트를 붙여 넣어 쓰는 방식이에요. 그래서 붙여 넣기 전에 스크립트가 안전한지부터 검사해요. 아래 ‘스크립트 복사’를 누르고, ChatGPT·Claude·Gemini 같은 AI 채팅에 이렇게 물어본 뒤 스크립트를 붙여 넣어요.

    threads.com 개발자 도구 콘솔에 붙여 넣으려는 스크립트야. 외부로 데이터를 보내거나, 쿠키·토큰을 읽거나, 나 대신 팔로우·게시 같은 행동을 하는 위험한 코드가 있는지 검사해 줘.

    이 사이트의 스크립트는 안전하지만, 누구의 스크립트도 그냥 믿지 마세요. 이 사이트처럼 꾸민 피싱 사이트가 계정을 빼앗는 악성 스크립트를 붙여 넣게 할 수 있어요. 개발자 도구에 무엇이든 붙여 넣기 전에는 꼭 검사하세요.

    chat · AIAI 채팅
    threads.com 콘솔에 붙여 넣기 전에 이 스크립트가 안전한지 검사해 줘.
    void (async () => { const CONFIG = { keep: [] }; …AI✓외부로 보내는 요청: 없음✓쿠키·토큰 읽기: 없음✓팔로우·언팔로우·게시: 없음✓난독화·eval: 없음화면을 읽고 표시만 하는 스크립트예요.
  2. 2

    PC에서 threads.com 로그인

    PC 브라우저(Chrome 권장)로 threads.com 에 로그인해 두면 돼요. 어느 화면에 있든 스크립트가 내 프로필로 이동해 “팔로워 OO명”을 눌러 팔로워·팔로잉 창을 열어요.

    threads.com/@acupofcommitthreads추천새로운 스레드검색메시지활동프로필인사이트커밋한잔 - 차아린acupofcommit페르소나:- 월가 애널리스트 → 커밋한잔 1번 직원- 하루에 커밋 1개 하면 된다고 해서 입사 제안 수락함팔로워 142명 · 최근 조회수 12만회프로필 편집스레드답글미디어리포스트새로운 소식을 공유해보세요.게시
  3. 3

    개발자 도구 콘솔 열기

    F12(맥은 ⌥⌘I)를 눌러 개발자 도구를 열고 Console 탭으로 가요.

    threads.com/@me팔로워139팔로잉141user_a맞팔로우user_b팔로잉user_c맞팔로우user_d팔로잉ElementsConsoleSources>F12Mac⌥⌘I
  4. 4

    스크립트 붙여 넣고 Enter

    검사한 그 스크립트를 콘솔에 붙여 넣고 Enter를 눌러요. Chrome이 붙여 넣기를 막으면 allow pasting 을 입력하고 Enter를 누른 뒤 다시 붙여 넣어요.

    threads.com/@meConsole⚠ 붙여 넣기가 막히면 아래처럼 입력하고 Enterallow pasting>allow pasting>void (async () => { try { const CONFIG = { keep: [], }; …})();Enter ↵
  5. 5

    결과 표 확인

    팔로워와 팔로잉 목록을 끝까지 내려 모은 뒤, 나를 팔로우하지 않는 계정과 프로필 주소를 표로 보여 줘요. 표 아래에는 복사하기 쉽게 계정 이름만 한 줄씩 나와요. 계정이 많으면 몇 분 걸려요.

    threads.com/@me팔로워: 139명 수집 · 팔로잉: 141명 수집나를 팔로우하지 않는 계정 3명:(index)계정주소0'user_b''https://www.threads.com/@user_b'1'user_d''https://www.threads.com/@user_d'2'user_e''https://www.threads.com/@user_e'user_buser_duser_e언팔로우는 하지 않았습니다. 팔로잉 목록을 내리며 '뒷삭 검거' 표시가…
  6. 6

    표시된 계정을 직접 언팔로우

    스크립트가 끝나면 팔로잉 목록에서 나를 팔로우하지 않는 계정 줄이 붉게 칠해지고 ‘뒷삭 검거(맞팔안한 친구)’가 붙어요. 목록을 내리며 표시된 계정의 ‘팔로잉’ → ‘언팔로우’를 눌러요. 특정 계정을 바로 찾으려면 ‘팔로잉’ 탭 제목을 한 번 누른 뒤 Cmd+F(Windows는 Ctrl+F)로 아이디를 찾아요. 표시는 새로 고치면 사라져요. 너무 빠르게 연달아 언팔로우하면 Meta가 비정상 활동으로 보고 잠시 작업을 제한할 수 있으니 천천히 정리하세요.

    threads.com/@me팔로워139팔로잉141user_a팔로잉뒷삭 검거(맞팔안한 친구)user_b팔로잉user_c팔로잉뒷삭 검거(맞팔안한 친구)user_d팔로잉
스크립트 만들기

맞팔이 아니어도 결과에서 뺄 계정이에요. 줄바꿈·쉼표로 나눠 적고, @이름이나 프로필 주소도 돼요.

목록을 보여 주고 화면에 표시만 해요. 아무도 언팔로우하지 않아요.

스크립트 미리보기
// 스레드: 내가 팔로우하는데 나를 팔로우하지 않는 계정 찾기
// https://threads-follow-checker.commit2.app 에서 만든 스크립트입니다. 이 브라우저 안에서만 돌고, 어떤 데이터도 밖으로 보내지 않습니다.
//
// 쓰는 법
// 1. threads.com 에 로그인한 탭에서 실행한다. 내 프로필 이동과 팔로워 창 열기는 스크립트가 한다.
// 2. 개발자 도구 콘솔에 이 스크립트 전체를 붙여 넣고 Enter.
// 3. 팔로잉 목록에서 "뒷삭 검거" 표시가 붙은 계정을 직접 언팔로우한다.
//    이 스크립트는 목록을 보여 주고 화면에 표시만 할 뿐, 아무도 언팔로우하지 않는다.

// 앞의 void 는 콘솔이 Promise {<pending>} 을 찍지 않게 한다. 진행 상황은 아래 안내로 보여 준다.
void (async () => {
  try {
    const CONFIG = {
      keep: [], // 맞팔이 아니어도 결과에서 뺄 계정
    };

    const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
    const rand = (a, b) => a + Math.random() * (b - a);
    // 대화상자가 여러 개면 팔로워·팔로잉 탭이 있는 것을 고른다.
    const dialog = () =>
      [...document.querySelectorAll('[role="dialog"]')].find((d) => d.querySelector('[role="tab"]')) ||
      document.querySelector('[role="dialog"]');
    // threads.com 탭에서만 돈다.
    if (!/(^|\.)threads\.(com|net)$/.test(location.hostname)) {
      return console.error("threads.com 탭에서 실행해 주세요. 지금 탭은 " + (location.hostname || location.href) + " 이에요.");
    }

    // 왼쪽 메뉴의 "프로필" 링크로 내 아이디를 알아낸다. 없으면 로그인하지 않은 것으로 본다.
    const profileLink = () =>
      [...document.querySelectorAll('a[href^="/@"]')].find(
        (a) =>
          /^(프로필|Profile)$/i.test((a.innerText || "").trim()) ||
          /^(프로필|Profile)$/i.test(a.getAttribute("aria-label") || "") ||
          a.querySelector('[aria-label="프로필"], [aria-label="Profile"]'),
      );
    const followersOpener = () =>
      [...document.querySelectorAll('[role="button"], a, span')].find((e) =>
        /^(팔로워\s*[\d,.]+\s*[천만]?\s*명|[\d,.]+\s*[KkM]?\s*followers?)$/i.test((e.innerText || "").trim()),
      );

    if (!dialog()) {
      const link = profileLink();
      if (!link) {
        return console.error("로그인되어 있지 않은 것 같아요. threads.com 에 로그인한 뒤 다시 실행해 주세요.");
      }
      const me = link.getAttribute("href").slice(2).split(/[/?]/)[0];
      const onMyProfile = () => location.pathname.toLowerCase().startsWith("/@" + me.toLowerCase());
      if (!onMyProfile()) {
        // 주소를 직접 바꾸면 페이지가 새로 로드되며 이 스크립트도 멈추므로, 링크를 눌러 스레드 안에서 이동한다.
        console.log("👤 내 프로필(@" + me + ")로 이동합니다…");
        link.click();
        for (let k = 0; k < 40 && !(onMyProfile() && followersOpener()); k++) await sleep(250);
      }
      // 팔로워 창이 닫혀 있으면 프로필의 "팔로워 OO명"(영문 "OO followers")을 눌러 연다.
      for (let k = 0; k < 20 && !followersOpener(); k++) await sleep(250);
      const opener = followersOpener();
      if (opener) {
        console.log("👆 팔로워 창을 엽니다…");
        (opener.closest('[role="button"], a') || opener).click();
        for (let k = 0; k < 20 && !dialog(); k++) await sleep(250);
      }
    }
    if (!dialog()) return console.error("팔로워 창을 열지 못했어요. 내 프로필에서 “팔로워 OO명”을 직접 누른 뒤 다시 실행해 주세요.");

    const startedAt = Date.now();
    const elapsed = () => Math.round((Date.now() - startedAt) / 1000) + "초";
    console.log("🔍 맞팔 확인을 시작합니다. 계정 수에 따라 몇 분 걸릴 수 있어요.");
    console.log("⏳ 끝날 때까지 이 탭과 팔로워 창을 닫지 말고 기다려 주세요.");

    const tabs = () => [...dialog().querySelectorAll('[role="tab"]')];
    const tabOf = (i) => tabs()[i]; // 0 = Followers(팔로워), 1 = Following(팔로잉)
    // 스레드는 창 틀을 먼저 띄우고 탭은 조금 뒤에 그리므로, 탭 두 개가 보일 때까지 기다린다.
    for (let k = 0; k < 40 && tabs().length < 2; k++) await sleep(250);
    if (tabs().length < 2) {
      return console.error("팔로워 창의 팔로워·팔로잉 탭을 찾지 못했어요. 창을 닫았다 다시 연 뒤 실행해 주세요.");
    }
    const countOf = (i) => Number((tabOf(i)?.innerText.match(/[\d,]+/) || ["0"])[0].replace(/,/g, ""));
    const scroller = () =>
      [...dialog().querySelectorAll("*")].find(
        (e) => /(auto|scroll)/.test(getComputedStyle(e).overflowY) && e.scrollHeight > e.clientHeight + 5,
      );
    const usernamesInView = () =>
      [...dialog().querySelectorAll('a[href^="/@"]')].map((a) => a.getAttribute("href").slice(2).split(/[/?]/)[0]);

    async function openTab(i) {
      const t = tabOf(i);
      if (!t) throw new Error("팔로워·팔로잉 탭이 사라졌어요");
      (t.querySelector('[role="button"]') || t).click();
      await sleep(1500);
    }

    // 무한 스크롤: 화면의 80%씩 내리면서 보이는 계정을 모은다(한 번에 끝으로 건너뛰면 중간 줄을 놓친다).
    // 바닥에 닿은 상태에서 새 계정이 연속으로 안 늘면 끝으로 본다. 탭 숫자보다 적게 모였으면 다음 묶음을
    // 늦게 불러오는 중일 수 있어 더 오래 기다리고, 조금 올렸다 내려 다시 불러오게 한다.
    async function collect(i, label) {
      await openTab(i);
      const total = countOf(i);
      console.log("⏳ " + label + " 수집 중… (화면 표시 " + total + "명)");
      const sc = scroller();
      if (sc) sc.scrollTop = 0;
      await sleep(800);
      const set = new Set();
      let still = 0;
      let rounds = 0;
      let logged = 0;
      while (still < (set.size >= total ? 4 : 10)) {
        const before = set.size;
        usernamesInView().forEach((u) => set.add(u));
        // 몇 번 스크롤할 때마다 지금까지 모은 수를 알려 준다.
        rounds += 1;
        if (rounds % 4 === 0 && set.size !== logged) {
          logged = set.size;
          console.log("   " + label + " " + set.size + "/" + total + "명 모음 · " + elapsed());
        }
        const s = scroller();
        const atBottom = !s || s.scrollTop + s.clientHeight >= s.scrollHeight - 2;
        if (s && atBottom && set.size === before) {
          s.scrollTop -= s.clientHeight;
          await sleep(300);
          s.scrollTop = s.scrollHeight;
        } else if (s) {
          s.scrollTop += s.clientHeight * 0.8;
        }
        await sleep(rand(1000, 1800));
        still = set.size === before && atBottom ? still + 1 : 0;
      }
      if (total && set.size < total * 0.97) {
        console.warn("⚠ " + label + "를 다 모으지 못했을 수 있어요 (" + set.size + "/" + total + "명). 탭 숫자에는 목록에 안 보이는 비활성 계정도 들어가지만, 차이가 크면 다시 실행해 보세요.");
      }
      console.log(`✓ ${label}: ${set.size}명 수집 (화면 표시 ${countOf(i)}명)`);
      return set;
    }

    const followers = await collect(0, "팔로워");
    const following = await collect(1, "팔로잉");
    console.log("🧮 계산 중…");
    const keep = new Set(CONFIG.keep);
    const targets = [...following].filter((u) => !followers.has(u) && !keep.has(u));

    console.log(`나를 팔로우하지 않는 계정 ${targets.length}명:`);
    console.table(targets.map((u) => ({ 계정: u, 주소: `https://www.threads.com/@${u}` })));
    window.__nonFollowers = targets;

    // 계정 이름만 따로 찍어 복사하기 쉽게 한다.
    if (targets.length > 0) console.log(targets.join("\n"));

    // 팔로잉 목록에서 나를 팔로우하지 않는 계정 줄에 "뒷삭 검거"를 표시한다.
    // 화면 모양만 바꾸고 스레드에는 아무것도 보내지 않는다. 새로 고치면 사라진다.
    // 스레드 화면을 그리는 React 와 부딪히지 않게 요소를 끼워 넣지 않고 속성과 CSS(::after)로만 표시한다.
    // 목록은 스크롤할 때마다 줄을 다시 그리고 같은 요소에 다른 계정을 넣기도 하므로, 바뀔 때마다 다시 맞춘다.
    console.log("🖍️ 팔로잉 목록에 '뒷삭 검거' 표시 중…");
    const targetSet = new Set(targets);
    const ROW = "data-nonfollower";
    const NAME = "data-nonfollower-name";
    window.__nonFollowerObserver?.disconnect();
    document.getElementById("nonfollower-style")?.remove();
    const style = document.createElement("style");
    style.id = "nonfollower-style";
    style.textContent = [
      "[" + ROW + "] { background: rgba(255, 59, 48, 0.14) !important; box-shadow: inset 3px 0 0 #ff3b30; }",
      "[" + NAME + "]::after { content: '뒷삭 검거(맞팔안한 친구)'; display: inline-block; margin-left: 6px; padding: 1px 6px;" +
        " border-radius: 999px; background: #ff3b30; color: #fff; font-size: 11px; font-weight: 700; line-height: 16px; vertical-align: middle; }",
    ].join("\n");
    document.head.appendChild(style);

    const userOf = (a) => a.getAttribute("href").slice(2).split(/[/?]/)[0];
    const rowOfLink = (a) => {
      let el = a;
      while (el && el !== dialog() && !el.querySelector('[role="button"]')) el = el.parentElement;
      return el && el !== dialog() ? el : null;
    };
    const mark = () => {
      const d = dialog();
      if (!d) return;
      // 이전에 붙인 표시가 지금 그 자리의 계정과 안 맞으면 뗀다.
      d.querySelectorAll("[" + ROW + "]").forEach((row) => {
        const a = row.querySelector('a[href^="/@"]');
        if (!a || userOf(a) !== row.getAttribute(ROW)) row.removeAttribute(ROW);
      });
      d.querySelectorAll("[" + NAME + "]").forEach((a) => {
        if (userOf(a) !== a.getAttribute(NAME)) a.removeAttribute(NAME);
      });
      d.querySelectorAll('a[href^="/@"]').forEach((a) => {
        const u = userOf(a);
        if (!targetSet.has(u)) return;
        const row = rowOfLink(a);
        if (row) row.setAttribute(ROW, u);
        // 사진 링크가 아니라 아이디 글자가 있는 링크에만 붙인다.
        if (a.innerText.trim().startsWith(u)) a.setAttribute(NAME, u);
      });
    };
    let queued = false;
    const observer = new MutationObserver(() => {
      if (queued) return;
      queued = true;
      requestAnimationFrame(() => {
        queued = false;
        mark();
      });
    });
    // 목록이 줄 요소를 그대로 두고 링크 주소·글자만 바꿔 다른 계정을 보여 줄 때도 다시 맞추도록
    // href 속성과 글자 변경도 지켜본다(우리가 붙이는 data- 속성은 빼서 스스로를 다시 부르지 않게 한다).
    observer.observe(document.body, { childList: true, subtree: true, characterData: true, attributes: true, attributeFilter: ["href"] });
    window.__nonFollowerObserver = observer;
    mark();

    console.log("✅ 끝났습니다 (" + elapsed() + ").");
    console.log(
      "언팔로우는 하지 않았습니다. 팔로잉 목록을 내리며 '뒷삭 검거' 표시가 붙은 계정을 직접 언팔로우하세요. 표시는 새로 고치면 사라집니다.",
    );
  } catch (error) {
    // 예상 못 한 에러는 스레드의 에러 처리(ErrorUtils) 대신 여기서 알기 쉽게 알린다.
    console.error("스크립트가 멈췄어요: " + (error && error.message ? error.message : error));
    console.error("스레드 탭을 새로 고친 뒤 다시 실행해 주세요. 계속되면 이 메시지를 알려 주세요.");
  }
})();

자주 묻는 질문

안전한가요?

이 사이트는 계정 정보를 받지 않고, 스크립트는 내 브라우저 안에서 스레드 화면을 읽기만 해요. 외부로 보내는 코드가 없다는 건 미리보기에서 직접 확인할 수 있어요.

자동으로 언팔로우해 주지는 않나요?

Meta는 짧은 시간에 몰리는 언팔로우를 자동화로 보고 계정을 제한할 수 있어요. 계정을 지키려고 스크립트는 찾기만 하고, 언팔로우는 직접 하도록 했어요.

모바일에서도 되나요?

안 돼요. 개발자 도구 콘솔이 필요해서 PC 브라우저에서만 쓸 수 있어요.

결과 숫자가 탭에 보이는 숫자와 달라요.

탭의 팔로워·팔로잉 수에는 목록에 보이지 않는 비활성·제한 계정이 들어갈 수 있어요. 스크립트는 목록에 실제로 보이는 계정만 세요.

도중에 멈추고 싶어요.

스레드 탭을 새로 고치면 스크립트가 멈춰요.

커밋한잔의 다른 도구

YouTube Playlist MakerYouTube 링크 여러 개를 재생목록 링크 하나로