Skip to content

Instantly share code, notes, and snippets.

@HyperCrowd
Last active June 8, 2025 02:51
Show Gist options
  • Save HyperCrowd/edc9b461ec23cf2454ea4d1e910fd1c6 to your computer and use it in GitHub Desktop.
Save HyperCrowd/edc9b461ec23cf2454ea4d1e910fd1c6 to your computer and use it in GitHub Desktop.

Crippling Facebook

Facebook works with advertisers to target you. These instructions are one of the many ways to begin crippling that relationship. When AI targeting is crippled, your psychosecurity improves :)

  1. On your desktop machine, goto https://accountscenter.facebook.com/ads/audience_based_advertising
  2. Maximize the browser window
  3. Press F12 and click on the Console tab
  4. Select the code below, copy it, paste it upon the Console line (The area next to the > character in the Console window), and press enter:
const i18n = {
  en: {
    seeMore: 'See more',
    theyUploaded: 'They uploaded or used a list to reach you.',
    dontAllow: 'Don\'t allow',
    back: 'Back'
  },
  br: {
    seeMore: 'Ver mais',
    theyUploaded: 'O anunciante carregou',
    dontAllow: 'Não permitir',
    back: 'Voltar'
  }
  // Feel free to add more languages here!
}

const delay = () => new Promise(resolve => setTimeout(resolve, 3000))

const getElementsByAriaLabel = (label) => document.querySelectorAll(`[aria-label="${label}"]`)

const getElementsByRoleAndWidth = (role, width) => 
    Array.from(document.querySelectorAll(`[role="${role}"]`)).filter(el => el.clientWidth === width)

const getElementsByText = (text) => Array.from(document.querySelectorAll('*')).filter(el => 
    Array.from(el.childNodes).some(node => node.nodeType === Node.TEXT_NODE && node.textContent.includes(text))
)

const wait = async (watcher, timeout = 10000, interval = 100) => {
  const startTime = Date.now()

  return new Promise((resolve, reject) => {
    const checkForElement = () => {
      const elements = watcher()

      if (elements.length > 0) {
        clearInterval(intervalId)
        resolve(elements);
      } else if (Date.now() - startTime > timeout) {
        clearInterval(intervalId)
        reject(new Error(`Timeout: Elements not found`))
      }
    }

    const intervalId = setInterval(checkForElement, interval)

    checkForElement()
  })
}

async function decouple(options = {}) {
  const offset = options.offset
  const lang = options.lang || 'en'

  getElementsByText(i18n[lang].seeMore).forEach(el => el.click())
  await delay()

  const ads = getElementsByRoleAndWidth('listitem', 508).slice(offset)
  let i = 0

  for (const ad of ads) {
    console.log(i, ad.childNodes[0].textContent)
    ad.childNodes[0].click()
    await delay()

    const a = await wait(() => getElementsByText(i18n[lang].theyUploaded))
    a[0].click()
    await delay()

    const b = await wait(() => getElementsByText(i18n[lang].dontAllow))
    b[1].click()
    await delay()

    const c = await wait(() => getElementsByText(i18n[lang].dontAllow))
    c[0].click()
    await delay()

    const d = await wait(() => getElementsByAriaLabel(i18n[lang].back))
    d[2].click()
    await delay()

    const e = await wait(() => getElementsByAriaLabel(i18n[lang].back))
    e[2].click()
    await delay()

    i += 1
  }
}

decouple()
  1. Watch as your slowly unsubscribe from all advertisers
  2. Don't click or interact with the browser at all while this is going on. Go do laundry or something.
  3. Enjoy cutting off advertisers that hate you :)
  4. If you have hundreds of advertisers, this script will most likely not get all of them on the first pass. In the console are numbers next to the name of each advertiser. You can restart the process manually and specify the offset based on the last number in the console:
// If the last console output was "250 Dick's Sporting Goods", then do the following:
decouple({ offset: 250 })
  1. If you want to use another language, check out the i18n object and pick your language from one of it's keys. For example, to deal with Brazilian (br), you'd use the following:
decouple({ lang: 'br' })

To do and offset in a different language, it would look ilke this:

decouple({ offset: 250, lang: 'br' })
``
@sierisimo
Copy link

I'm not sure if it's intentional but there's a small error here:

const c = await wait(() => getElementsByText(i18n[lang]..dontAllow))

It has an additional dot.

@HyperCrowd
Copy link
Author

I'm not sure if it's intentional but there's a small error here:

const c = await wait(() => getElementsByText(i18n[lang]..dontAllow))

It has an additional dot.

fixed!

thanks!

@exabrial
Copy link

exabrial commented Jul 9, 2024

Does anyone have similar scripts for Google, Instagram, or Apple

@HyperCrowd
Copy link
Author

Does anyone have similar scripts for Google, Instagram, or Apple

A worthy expansion! Given time and focus, I might get around to that :)

@dyanakiev
Copy link

I can't open the url, it says page is not available.

CleanShot 2024-08-21 at 19 13 34

@HyperCrowd
Copy link
Author

HyperCrowd commented Aug 22, 2024

I can't open the url, it says page is not available.

CleanShot 2024-08-21 at 19 13 34

I think what we are seeing is that many features of Facebook are geolocked, so different regions won't have access to certain pages

If you come in through a US VPN, you might be able to see the link

@knh1
Copy link

knh1 commented Oct 24, 2024

Trying this today, I had to change the ads listitem width from 508 to 560, but it still only operates on one item at a time. When it goes back to the main list and tries to click the next item, the page reloads which halts the JS execution, and the console throws the following error:

The connection to wss://gateway.facebook.com/ws/realtime?x-dgw-appid={redacted}&x-dgw-app-stream-group=group1 was interrupted while the page was loading.

I'm guessing fb made some backend changes since this was introduced. Any way to circumvent this?

@exabrial
Copy link

exabrial commented Jun 8, 2025

Thank you a ton for publishing this! Any recent updates? the code as is seems to stall, not sure why. There's now a "See More" button you have to click after clicking the back button, I have a feeling that might be the problem

@exabrial
Copy link

exabrial commented Jun 8, 2025

// Crippling Facebook — fully dynamic with per-iteration requery to avoid stale elements

const i18n = {
  en: {
    seeMore: 'See more',
    theyUploaded: 'They uploaded or used a list to reach you.',
    dontAllow: 'Don\'t allow',
    allow: 'Allow',
    back: 'Back'
  },
  br: {
    seeMore: 'Ver mais',
    theyUploaded: 'O anunciante carregou',
    dontAllow: 'Não permitir',
    allow: 'Permitir',
    back: 'Voltar'
  }
};

const delay = () => new Promise(res => setTimeout(res, 3000));

const getElementsByAriaLabel = label =>
  Array.from(document.querySelectorAll(`[aria-label="${label}"]`));

const getElementsByText = text =>
  Array.from(document.querySelectorAll('*')).filter(el =>
    Array.from(el.childNodes).some(
      node => node.nodeType === Node.TEXT_NODE && node.textContent.includes(text)
    )
  );

const wait = async (watcher, timeout = 10000, interval = 100) => {
  const start = Date.now();
  return new Promise((resolve, reject) => {
    const iv = setInterval(() => {
      const els = watcher();
      if (els.length) {
        clearInterval(iv);
        resolve(els);
      } else if (Date.now() - start > timeout) {
        clearInterval(iv);
        reject(new Error('Timeout: Elements not found'));
      }
    }, interval);
  });
};

async function decouple(options = {}) {
  const offset = options.offset || 0;
  const lang   = options.lang   || 'en';

  // 1) Find all ad URLs once
  const allAnchors = () =>
    Array.from(document.querySelectorAll('a[href*="/ads/audience_based_advertising/"]'));
  const allUrls = () => Array.from(new Set(
    allAnchors().map(a => {
      const u = new URL(a.href, window.location.origin);
      return u.pathname + u.search;
    })
  ));

  // 2) Keep track of which we've done
  const clicked = new Set();
  let index = 0;

  // 3) Main loop
  while (true) {
    // a) Expand list
    console.log('Expanding all "See more" links…');
    getElementsByText(i18n[lang].seeMore).forEach(el => el.click());
    await delay();

    // b) Refresh URLs and pick next
    const urls = allUrls().slice(offset);
    const suffix = urls.find(s => !clicked.has(s));
    if (!suffix) {
      console.log('All advertisers processed.');
      break;
    }

    // c) Locate its element fresh
    const anchor = allAnchors().find(a => a.getAttribute('href').endsWith(suffix));
    const adItem = anchor.closest('[role="listitem"]');
    console.log(`\n=== Ad #${index} ===`);
    console.log('Suffix   :', suffix);
    console.log('Full href:', anchor.href);

    // d) Prevent real navigation, open side-panel
    const opener = adItem.querySelector('a[role="link"]') || anchor;
    opener.addEventListener('click', e => e.preventDefault(), { once: true });
    console.log('Clicking ad container (navigation prevented)');
    opener.click();
    await delay();

    // e) Click “They uploaded…”
    console.log(`Clicking "${i18n[lang].theyUploaded}"`);
    const up = await wait(() => getElementsByText(i18n[lang].theyUploaded));
    up[0].click();
    await delay();

    // f) Disallow if possible
    console.log('Attempting to disallow this advertiser');
    const donts = getElementsByText(i18n[lang].dontAllow);
    if (donts.length >= 2) {
      console.log(`Clicking "${i18n[lang].dontAllow}" twice`);
      donts[1].click();
      await delay();
      const nextDont = await wait(() => getElementsByText(i18n[lang].dontAllow));
      nextDont[0].click();
      await delay();
    } else {
      console.log(`No "${i18n[lang].dontAllow}" button; likely already disallowed.`);
    }

    // g) Close detail pane
    for (let pass = 1; pass <= 2; pass++) {
      const backs = getElementsByAriaLabel(i18n[lang].back);
      if (backs.length) {
        console.log(`Clicking back arrow (pass ${pass})`);
        backs[backs.length - 1].click();
        await delay();
      }
    }

    // h) Mark done and advance
    clicked.add(suffix);
    index++;
  }
}

decouple();

Few issues found and fixed!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment