Last active
August 11, 2018 12:16
-
-
Save pocesar/4f51298da5698886e0c58997c4b101fe to your computer and use it in GitHub Desktop.
Javascript: delete all facebook posts from activity log
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// go to https://www.facebook.com/your_page_or_profile_name/allactivity?privacy_source=activity_log&log_filter=all | |
// load the maximum number of items you can before | |
// then open up the console and execute this code | |
// will take care of errors, successes and timeouts. it looks like a really proficient human clicking stuff | |
// it's messy and might break at any time, made for English version, but it's working as of august 2018 | |
(async function f() { | |
'use strict' | |
// simple exponential backoff reusable helper | |
const backoff = () => { | |
let count = 0 | |
return () => (Math.pow(2, count++)) | |
} | |
const clickAll = (set) => set.forEach((s) => s['click']()) | |
const race = (promises) => (new Promise((resolve) => promises.forEach((promise) => promise.then(resolve)))) | |
// wait some miliseconds | |
const waiter = (time) => (new Promise((resolve) => (setTimeout(resolve, time)))) | |
// common wrapper for tasks that should expire after some tries | |
const backoffPromise = (predicate, label = 'timeout', times = 10) => (new Promise((resolve, reject) => { | |
const b = backoff() | |
setTimeout(function retry() { | |
if (!times--) { | |
return reject(new Error(label)) | |
} | |
const result = predicate() | |
if (result) { | |
return resolve(result) | |
} | |
setTimeout(retry, b()) | |
}, 0) | |
})) | |
// wait for element to disappear (usually on modal). throws if times out, returns true | |
const waitForClose = (selector) => (backoffPromise(() => { | |
const element = document.querySelectorAll(selector) | |
return element && element.length > 0 | |
}, selector, 5)) | |
// wait for element (usually on modal). throws if times out, returns elements found | |
const waitForAvailability = (selector, text, times = 12, label = selector) => (backoffPromise(() => { | |
const elements = document.querySelectorAll(selector) | |
if (!elements || elements.length === 0) return false | |
let out = [] | |
for (let element of elements) { | |
if (element) { | |
if (text === null || element['innerText'] === text) { | |
out.push(element) | |
} | |
} | |
} | |
return out | |
}, label, times)) | |
const delBtn = '.layerConfirm.uiOverlayButton' // Delete button | |
const closeModal = 'form .layerCancel.uiOverlayButton' // Cancel / Delete modal | |
const cancelBtn = '._1yv .layerCancel' // error / failed modal | |
const menuBtn = 'a._42ft._42fu._4-s1._2agf._4o_4._p._42gx' | |
const actionsBtns = 'a._54nc' | |
const dialog = '._1yv[role="dialog"]' | |
let lastCount = 0 | |
console.log('triggering menus') | |
for (let menu of document.querySelectorAll(menuBtn)) { | |
if (!menu.querySelector('.sp_In7zzQ3ECOk.sx_042959')) { // hidden from timeline class | |
menu['click']() | |
await backoffPromise(() => { | |
const last = document.querySelectorAll(actionsBtns).length | |
if (last > lastCount) { return true } | |
lastCount = last | |
return false | |
}) | |
} | |
} | |
console.log('done triggering menus, will try to act') | |
const actions = document.querySelectorAll(actionsBtns) // actions | |
for (let action of actions) { | |
if (action['innerText'] === 'Hidden from Page' || action['innerText'] === 'Remove Reaction') { | |
action['click']() | |
try { | |
if ((await waitForAvailability(dialog, null)).length) { | |
const close = await race([ | |
waitForAvailability(cancelBtn, 'Close', 3), | |
waitForAvailability(closeModal, 'Close', 3), | |
waitForAvailability(closeModal, 'Cancel', 3), | |
waitForAvailability(cancelBtn, 'Cancel', 3) | |
]) | |
clickAll(close) | |
await waitForClose(dialog) | |
} | |
} catch (e) { } | |
} | |
if (action['innerText'] === 'Delete') { | |
action['click']() | |
try { | |
const del = await waitForAvailability(delBtn, 'Delete') | |
// modal is opened | |
clickAll(del) | |
// wait for result | |
try { | |
const close = await waitForAvailability(cancelBtn, 'Close', 5) | |
// seems an error happened, close the modal | |
clickAll(close) | |
const cancel = await waitForAvailability(closeModal, 'Cancel', 2) | |
// close current modal that errored out | |
clickAll(cancel) | |
} catch (e) { | |
if (e.message !== closeModal) { | |
const cancel = await waitForAvailability(closeModal, 'Cancel', 2) | |
clickAll(cancel) | |
} | |
// no error happened, let's wait for the modal to close | |
await race([ | |
waitForClose(dialog), | |
waitForClose(closeModal), | |
]) | |
} | |
} catch (e) { | |
// modal didn't close, last brute force try | |
try { | |
const close = await race([ | |
waitForAvailability(cancelBtn, 'Close', 5), | |
waitForAvailability(closeModal, 'Close', 5), | |
waitForAvailability(closeModal, 'Cancel', 5), | |
waitForAvailability(cancelBtn, 'Cancel', 5) | |
]) | |
clickAll(close) | |
await waitForClose(dialog) | |
} catch (e) { | |
console.log(e) | |
} | |
} | |
} | |
await waiter(500) // throttle requests, Facebook doesn't like too much, too fast | |
} | |
alert('done?') | |
})() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment