Skip to content

Instantly share code, notes, and snippets.

@HyperCrowd
Last active June 30, 2024 22:02
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' })
``
@elliots
Copy link

elliots commented Jun 22, 2024

I had to fiddle with it to get it to work (on firefox)

  • wrap in an async function so await is ok
  • click "See more" to show all the ads not just first 5
  • only continue if they used a list
  • click 'dont allow' only when its on the page, it might already have been disabled
(async function() {

const wait = () => new Promise(resolve => setTimeout(resolve, 1500))
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))
)

getElementsByText('See more').forEach(el => el.click())
await wait()

const ads = getElementsByRoleAndWidth('listitem', 508)
console.log(`Found ${ads.length} ads`)

for (const ad of ads) {
    console.log('ad: ' + ad.childNodes[0].textContent)
    ad.childNodes[0].click()
    await wait()
    const usedList = getElementsByText('They uploaded or used a list to reach you.').length
    if (usedList) {
        console.log('ad used list')
        getElementsByText('They uploaded or used a list to reach you.')[0].click()
        await wait()
        getElementsByText('Don\'t allow').forEach(el => el.click())
        await wait()
        getElementsByAriaLabel('Back')[2].click()
        await wait()
    }
    getElementsByAriaLabel('Back')[2].click()
    await wait()
}

})()

@kristof-mattei
Copy link

kristof-mattei commented Jun 22, 2024

Made 2 changes: added a wait after opening the ad itself, and made the click on the Don't allow buttons undefined-safe:

const wait = () => new Promise(resolve => setTimeout(resolve, 1500))
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 ads = getElementsByRoleAndWidth('listitem', 508)

for (const ad of ads) {
    console.log(ad.childNodes[0].textContent)
    ad.childNodes[0].click()
    await wait()
    getElementsByText('They uploaded or used a list to reach you.')[0].click()
    await wait()
    getElementsByText('Don\'t allow')?.[1]?.click()
    getElementsByText('Don\'t allow')?.[0]?.click()
    await wait()
    getElementsByAriaLabel('Back')[2].click()
    await wait()
    getElementsByAriaLabel('Back')[2].click()
    await wait()
}

@HyperCrowd
Copy link
Author

I had to fiddle with it to get it to work (on firefox)

  • wrap in an async function so await is ok
  • click "See more" to show all the ads not just first 5
  • only continue if they used a list
  • click 'dont allow' only when its on the page, it might already have been disabled
(async function() {

const wait = () => new Promise(resolve => setTimeout(resolve, 1500))
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))
)

getElementsByText('See more').forEach(el => el.click())
await wait()

const ads = getElementsByRoleAndWidth('listitem', 508)
console.log(`Found ${ads.length} ads`)

for (const ad of ads) {
    console.log('ad: ' + ad.childNodes[0].textContent)
    ad.childNodes[0].click()
    await wait()
    const usedList = getElementsByText('They uploaded or used a list to reach you.').length
    if (usedList) {
        console.log('ad used list')
        getElementsByText('They uploaded or used a list to reach you.')[0].click()
        await wait()
        getElementsByText('Don\'t allow').forEach(el => el.click())
        await wait()
        getElementsByAriaLabel('Back')[2].click()
        await wait()
    }
    getElementsByAriaLabel('Back')[2].click()
    await wait()
}

})()

Good idea! I've modified wait to wait for a selector to return results. The rest of your features I will incorporate!

@HyperCrowd
Copy link
Author

Made 2 changes: added a wait after opening the ad itself, and made the click on the Don't allow buttons undefined-safe:

const wait = () => new Promise(resolve => setTimeout(resolve, 1500))
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 ads = getElementsByRoleAndWidth('listitem', 508)

for (const ad of ads) {
    console.log(ad.childNodes[0].textContent)
    ad.childNodes[0].click()
    await wait()
    getElementsByText('They uploaded or used a list to reach you.')[0].click()
    await wait()
    getElementsByText('Don\'t allow')?.[1]?.click()
    getElementsByText('Don\'t allow')?.[0]?.click()
    await wait()
    getElementsByAriaLabel('Back')[2].click()
    await wait()
    getElementsByAriaLabel('Back')[2].click()
    await wait()
}

Elvis to the rescue! Good idea!

@csisoap
Copy link

csisoap commented Jun 23, 2024

Facebook only listed 4 advertiser for me. I have to click See more to get a full list of it.

EDIT: I see a solution from @elliots . Great work.

@cba85
Copy link

cba85 commented Jun 23, 2024

Am I the only one who can't access https://accountscenter.facebook.com/ads/audience_based_advertising ?

This page isn’t available
The link may be broken, or the page may have been removed. Check to see if the link you’re trying to open is correct.

@NGRhodes
Copy link

Am I the only one who can't access https://accountscenter.facebook.com/ads/audience_based_advertising ?

This page isn’t available
The link may be broken, or the page may have been removed. Check to see if the link you’re trying to open is correct.

For me the URL is https://www.facebook.com/adpreferences/ad_settings/?section=audience_based_advertising

@cba85
Copy link

cba85 commented Jun 23, 2024

Am I the only one who can't access https://accountscenter.facebook.com/ads/audience_based_advertising ?

This page isn’t available
The link may be broken, or the page may have been removed. Check to see if the link you’re trying to open is correct.

For me the URL is https://www.facebook.com/adpreferences/ad_settings/?section=audience_based_advertising

Thank you! It works now 👍

@drjasonharrison
Copy link

Is there a way to restart the run of this script after the first half of the list of advertisers has been processed? My browser got to about 150/299 advertisers.

@HyperCrowd
Copy link
Author

HyperCrowd commented Jun 23, 2024

Is there a way to restart the run of this script after the first half of the list of advertisers has been processed? My browser got to about 150/299 advertisers.

Yes, I have updated the code to address this :)

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 })

@ChangheeOh
Copy link

ChangheeOh commented Jun 23, 2024

I can't access the above URL.

URL: https://accountscenter.facebook.com/ads/audience_based_advertising

This page isn’t available
The link may be broken, or the page may have been removed. Check to see if the link you’re trying to open is correct.

@luizfzs
Copy link

luizfzs commented Jun 24, 2024

I also had to make some adjustments, as it kept saying that the it couldn't click on the getElementsByAriaLabel('Back')[2].click() element.
In addition, I've changed the labels to Portuguese to fit my case.

⚠️ If your language is not English, you'll have to change the following section to get it working.
🇧🇷 For Brazilian Portuguese, use:

// language-specific configs
const seeMore = 'Ver mais'
const theyUploaded = 'O anunciante carregou'
const dontAllow = 'Não permitir'
const back = 'Voltar'
(async function() {

const wait = () => new Promise(resolve => setTimeout(resolve, 1500))
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))
)

// language-specific configs
const seeMore = 'See more'
const theyUploaded = 'They uploaded or used'
const dontAllow = 'Don\'t allow'
const back = 'Back'

getElementsByText(seeMore).forEach(el => el.click())
await wait()

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

for (const ad of ads) {
    console.log(`ad ${i + 1} of ${ads.length}: ` + ad.childNodes[0].textContent)
    ad.childNodes[0].click()
    await wait()

    const usedList = Array.from(document.querySelectorAll('span'))
  .find(el => el.textContent.includes(theyUploaded))
    
    if (usedList) {
        console.log('ad used list')
        usedList.click()
        await wait()
        setTimeout(() => {}, 500)

        el = getElementsByText(dontAllow)
        if (el.length > 0) el.forEach(el => el.click())
        await wait()
        setTimeout(() => {}, 500)
        
        getElementsByAriaLabel(back)[2].click()
        await wait()
        setTimeout(() => {}, 500)
    } else {
      console.log(usedList)
    }

    await wait()
    getElementsByAriaLabel(back)[2].click()
    await wait()
    i++
}

})()

@HyperCrowd
Copy link
Author

Thanks! I've added naive multilingual support and an example in the instructions 👍

@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!

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