Last active
April 23, 2023 23:10
-
-
Save phistuck/97adcd14809980c54d468e925b8089be to your computer and use it in GitHub Desktop.
Automation of contact deletion or addition via the Google Contacts page
This file contains 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
// Public domain, or MIT if not legal. | |
// Run using the console on contacts.google.com | |
// Approve notifications | |
/** @type {(xPath: string) => HTMLElement[]} */ | |
var $x = $x; | |
/** @type {(cssSelector: string) => HTMLElement} */ | |
var $ = $; | |
localStorage.contactsToProcess = JSON.stringify([ | |
"email@example.com" | |
]); | |
var c = /** @type {(string | string[])[]} */ JSON.parse(localStorage.contactsToProcess) | |
var kActions = { | |
remove: 'Delete', | |
add: "Add to contacts" | |
}; | |
var stopProcessing = false; | |
runActionOnBatch(c, kActions.remove); | |
/** | |
* @param {(string | string[])[]} list | |
* @param {string} action | |
*/ | |
async function runActionOnBatch(list, action) | |
{ | |
while (list.length) | |
{ | |
console.log(list.length); | |
if (stopProcessing) | |
{ | |
throw new Error('Stopped everything'); | |
} | |
await wait(5000); | |
var email = list.pop(); | |
try | |
{ | |
await runAction(/** @type {string | string[]} */(email), action); | |
} catch (e) | |
{ | |
new Notification('Contact process failed', { | |
requireInteraction: true | |
}); | |
throw e; | |
} | |
localStorage.contactsToProcess = JSON.stringify(list); | |
} | |
new Notification('Finished contact processes', { requireInteraction: true }); | |
} | |
function resetFocus() | |
{ | |
document.body.click(); | |
const a = $('.Ax4B8.ZAGvjd'); | |
a.click(); | |
} | |
/** | |
* @param {string | string[]} email | |
*/ | |
function typeSearchQuery(email) | |
{ | |
const a = /** @type {HTMLInputElement} */ ($('.Ax4B8.ZAGvjd')); | |
a.focus(); | |
a.value = Array.isArray(email) ? email[0] : email; | |
var b = document.createEvent("HTMLEvents"); | |
b.initEvent("input", !0, !0); | |
a.dispatchEvent(b); | |
} | |
function runSearch() | |
{ | |
$('.gb_5e.gb_6e').click(); | |
} | |
/** | |
* @param {string | string[]} email | |
*/ | |
function search(email) | |
{ | |
resetFocus(); | |
typeSearchQuery(email); | |
runSearch(); | |
} | |
/** | |
* @param {number} time | |
*/ | |
function wait(time) | |
{ | |
return new Promise(resolve => setTimeout(resolve, time)); | |
} | |
/** | |
* @template {string | string[]} P | |
* @param {number} timeout | |
* @param {(...parameters: P[]) => Promise<boolean>} act | |
* @param {...P} parameters | |
*/ | |
async function retry(timeout, act, ...parameters) | |
{ | |
const startTime = Date.now(); | |
while (!(await act(...parameters))) | |
{ | |
if (timeout < Date.now() - startTime) | |
{ | |
throw new Error('Timeout!'); | |
} | |
if (stopProcessing) | |
{ | |
throw new Error('Stopped everything'); | |
} | |
await wait(200); | |
} | |
} | |
/** | |
* @param {string | string[]} email | |
*/ | |
async function selectContactRow(email) | |
{ | |
const relevantRows = getContactRow(email); | |
if (!relevantRows.length) | |
{ | |
return false; | |
} | |
relevantRows[0].click(); | |
return true; | |
} | |
/** | |
* @param {string | string[]} email | |
*/ | |
function getContactRow(email) | |
{ | |
const createEMailQuery = (oneEmail) => `text() = "${oneEmail}"`; | |
const emails = Array.isArray(email) ? email : [email]; | |
const emailQueries = emails.map(createEMailQuery).join(' or '); | |
return $x(`//*[contains(@style, "visibility: visible")]//*[@jscontroller = "PMaUNb" and .//*[${emailQueries}]]//*[@role="checkbox"]`); | |
} | |
/** | |
* @param {string | string[]} email | |
*/ | |
async function ensureNoContactRow(email) | |
{ | |
return !getContactRow(email).length; | |
} | |
async function runAddToContacts() | |
{ | |
await wait(200); | |
$('[style*="visibility: visible;"] [aria-label="Add to contacts"]').click(); | |
} | |
async function runMoreActions() | |
{ | |
await wait(200); | |
$('[style*="visibility: visible;"] [aria-label="More actions"]').click(); | |
} | |
async function runDelete() | |
{ | |
await wait(400); | |
const deleteButton = $('[style*="visibility: visible;"] [aria-label="Delete"]'); | |
deleteButton.dispatchEvent(new MouseEvent('mousedown', { | |
bubbles: true, | |
cancelable: true | |
})); | |
deleteButton.dispatchEvent(new MouseEvent('mouseup', { | |
bubbles: true, | |
cancelable: true | |
})); | |
deleteButton.click(); | |
} | |
async function confirmDeletion() | |
{ | |
await wait(500); | |
$x('//*[@class = "VfPpkd-vQzf8d" and text() = "Delete"]')[0].click(); | |
} | |
/** | |
* @param {string | string[]} email | |
* @param {string} action | |
*/ | |
async function runAction(email, action) | |
{ | |
search(email); | |
await retry(5000, selectContactRow, email); | |
if (action === 'Add to contacts') | |
{ | |
await runAddToContacts(); | |
} | |
else if (action === 'Delete') | |
{ | |
await runMoreActions(); | |
await runDelete(); | |
await confirmDeletion(); | |
await retry(5000, ensureNoContactRow, email); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment