How to write a google Hangout script?


#1

How can I write a script to call the scammers over and over again to waste their time?
This should be a simple script which calls the number and then press a number which the company sets for accessing their fake technicians.


#2

Is there a web version of the Hangouts Dialer app?? If there is, I can help you script something up.


#3

I could not find a web version. But it is possible to install the Android app in a PC emulator.
I find a solution by installing Macro Recorder (hxxp://www.macrorecorder.com/)
and recording the mouse and keyboard input. So it can repeat itself for eternity in a VM :smiley:
Obviously, it is not the same as call flooding but this will make them very angry because every 1 to 2 minutes there will be a call to the center.


#4

@Scamffxx I’ve found out that you can dial using https://hangouts.google.com the same way Hangouts Dialer allows you to dial.

  1. Click the “Phone Calls” button (Phone Icon on the left sidebar)
  2. Click “New Conversation”
  3. Type a valid number
  4. Click call

For the past couple of hours, I’ve developed an extensible script that performs this series of steps reliably. I can share the script here if you’d like, but I am still working on improving it.


#5

Perfect! I would like to have a working version!


#6

Here’s a sneak peak of my progress

Hangouts%20Automated%20Call%20copy

(sorry the gif is a bit choppy, I don’t wanna waste too much time fixing it)

I’ve put in a lot of effort to make sure the code is readable and easy to extend/modify. Currently, the code reliably places a call to a specific number and detects when the other side hangs up or disconnects (or a specified timeout happens, for example, timeout after 30 seconds).

It’s trivial now to make the code repeat after a certain event happens. Throw me some ideas on how the script might behave on a high level, I’ll try to make it smarter now.


#7
/**
 * Returns a promise that resolves a number of milliseconds in the future.
 * 
 * @param {number} duration 
 */
function sleep(duration) {
    return new Promise(resolve => setTimeout(resolve, duration))
}

/**
 * Finds the elements whose innerText match the regex.
 * 
 * @param {NodeList} elements 
 * @param {string} regex 
 */
function filterInnerText(elements, regex) {
    return Array.prototype.filter.call(elements, element => RegExp(regex).test(element.innerText));
}

/**
 * If `eventType` is 'click', 'mousedown', 'mouseup'
 * Then a 'MouseEvent' is dispatched on the `node`.
 * 
 * If `eventType` is 'keydown', 'keyup', 'keypress'
 * Then a 'KeyboardEvent' is dispatched on the `node` with keycode `args[0]`.
 * 
 * If `eventType` is ':set'
 * Then `node`'s attribute `args[0]` is set to `args[1]`.
 * 
 * @param {HTMLElement} node 
 * @param {string} eventType 'click', 'mousedown', 'mouseup', 'keydown', 'keyup', 'keypress'
 * @param {number} args[0]
 */
function dispatch(node, eventType, ...args) {
    switch (eventType) {
        case 'click':
        case 'mousedown':
        case 'mouseup': {
            const clickEvent = document.createEvent('MouseEvents');
            clickEvent.initEvent(eventType, true, true);
            node.dispatchEvent(clickEvent);
            break;
        }
            
        case 'keydown':
        case 'keyup':
        case 'keypress': {
            const [ keyCode ] = args;
            const keyboardEvent = document.createEvent("KeyboardEvent");
            const initMethod = typeof keyboardEvent.initKeyboardEvent !== 'undefined' ? "initKeyboardEvent" : "initKeyEvent";
            keyboardEvent[initMethod](
                eventType, // event type : keydown, keyup, keypress
                true, // bubbles
                true, // cancelable
                window, // viewArg: should be window
                false, // ctrlKeyArg
                false, // altKeyArg
                false, // shiftKeyArg
                false, // metaKeyArg
                keyCode, // keyCodeArg : unsigned long the virtual key code, else 0
                0 // charCodeArgs : unsigned long the Unicode character associated with the depressed key, else 0
            );
            node.dispatchEvent(keyboardEvent);
            break;
        }

        case ':set': {
            const [ key, value ] = args;
            node[key] = value;
            break;
        }

        default: {
            throw new Error('Unexpected `eventType` ' + eventType);
        }
    }
}

/**
 * Retries the dispatch every `interval` (seconds) until it works.
 * 
 * If the dispatch fails for more than the `timeout` (seconds), then it will
 * stop trying and raise the error to upstream.
 * 
 * `wait` (seconds) specifies the duration to sleep before trying to
 * dispatch the event.
 * 
 * @param {number} interval in seconds
 * @param {number} timeout in seconds
 * @param {number} wait in seconds
 * @param {function} elementSelector 
 * @param {array} args 
 */
async function forceDispatch({
    interval = 0.25,
    timeout = 1,
    wait = 0,
    optional = false,
}, elementSelector, ...args) {
    const element = await waitFor({
        interval,
        timeout,
        wait,
        optional,
    }, elementSelector);
    if (!optional) {
        if (element) dispatch(element, ...args);
        else throw new Error('Force dispatch failed');
    }
}

/**
 * Sleeps until the element appears.
 * 
 * @param {number} interval in seconds
 * @param {number} timeout in seconds
 * @param {number} wait in seconds
 * @param {function} elementSelector 
 */
async function waitFor({
    interval = 0.25,
    timeout = 1,
    wait = 0,
    optional = false,
}, elementSelector) {
    try {
        let elapsed = 0;
        await sleep(wait * 1000);
        while (true) {
            try {
                return elementSelector();
            } catch (err) {
                await sleep(interval * 1000);
                elapsed += interval;
                if (elapsed >= timeout) throw err;
            }
        }
    } catch (err) {
        if (!optional) throw err;
    }
}

function isConditionTrue(condition, element) {
    switch (condition) {
        case 'appear':
            return element;
        
            case 'disappear':
            return !element;

        default:
            throw new Error('Unexpected `condition` ' + condition);
    }
}

async function branch({
    interval = 2.5,
    timeout = Number.MAX_SAFE_INTEGER,
    timeoutCallback = () => {},
}, ...branches) {
    let elapsed = 0;
    while (true) {
        for (const [condition, elementSelector, callback] of branches) {
            let element = null;
            try {
                element = elementSelector();
            } catch (err) {}
            if (isConditionTrue(condition, element)) {
                callback();
                return;
            }
        }
        await sleep(interval * 1000);
        elapsed += interval;
        if (elapsed >= timeout) {
            timeoutCallback();
            return;
        }
    }
}

DOCUMENTS = {
    "Contacts and conversations": () => document.querySelector('iframe[aria-label="Contacts and conversations"]').contentDocument,

    "Dialer": (name) => document.querySelector('iframe[aria-label="' + name + '"]').contentDocument,
}

ELEMENTS = {
    "Phone Calls": () => document.querySelector('[role="button"][aria-label="Phone Calls"]'),

    "New conversation": () => DOCUMENTS["Contacts and conversations"]().querySelector('button'),

    "Country Code": () => DOCUMENTS["Contacts and conversations"]().querySelector('[role="listbox"]'),

    "Country Code Option": (code) => () => filterInnerText(DOCUMENTS["Contacts and conversations"]().querySelectorAll('[role="listbox"] > [role="option"]'), '\\b\\' + code + '\\b')[0],

    "Name, phone number": () => DOCUMENTS["Contacts and conversations"]().querySelector('input[placeholder="Name, phone number"]'),

    "Call": () => DOCUMENTS["Contacts and conversations"]().querySelector('[title="Call"]'),

    "Mute": (dialerName) => () => DOCUMENTS["Dialer"](dialerName).querySelector('[title="Mute"]'),

    "Calling": (dialerName) => () => filterInnerText(DOCUMENTS["Dialer"](dialerName).querySelectorAll('[aria-hidden="true"]'), 'Calling')[0],

    "In voice call": (dialerName) => () => filterInnerText(DOCUMENTS["Dialer"](dialerName).querySelectorAll('[aria-hidden="true"]'), 'In voice call')[0],

    "Number was unreachable": (dialerName) => () => filterInnerText(DOCUMENTS["Dialer"](dialerName).querySelectorAll('[aria-live="polite"]'), 'Call failed to connect or disconnected because the number was unreachable')[0],

    "Number was busy": (dialerName) => () => filterInnerText(DOCUMENTS["Dialer"](dialerName).querySelectorAll('[aria-live="polite"]'), 'Call failed to connect because the number was busy')[0],

    "Hang up": (dialerName) => () => DOCUMENTS["Dialer"](dialerName).querySelector('[title="Hang up"]'),

    "Close": (dialerName) => () => DOCUMENTS["Dialer"](dialerName).querySelector('[title="Close"]'),
}

function call(countryCode, number, name) {
    return new Promise(async (resolve) => {
        try {
            console.log('CALLING AGAIN...')
            console.log('Phone Calls')
            await forceDispatch({ wait: 0.25, timeout: 1 }, ELEMENTS["Phone Calls"], 'click')
            console.log('New conversation')
            await forceDispatch({ wait: 0.25, timeout: 1 }, ELEMENTS["New conversation"], 'click')
            console.log('Name, phone number: ' + countryCode + ' ' + number)
            await forceDispatch({ wait: 0.25, timeout: 1 }, ELEMENTS["Name, phone number"], ':set', 'value', countryCode + ' ' + number)
            console.log('Country Code')
            await forceDispatch({ wait: 0.25, timeout: 1 }, ELEMENTS["Country Code"], 'mousedown')
            console.log('Country Code: ' + countryCode)
            await forceDispatch({ wait: 0.25, timeout: 1 }, ELEMENTS["Country Code Option"](countryCode), 'mousedown')
            await forceDispatch({ wait: 0.25, timeout: 1 }, ELEMENTS["Country Code Option"](countryCode), 'mouseup')
            await forceDispatch({ wait: 0.25, timeout: 1 }, ELEMENTS["Country Code Option"](countryCode), 'mouseup')
            console.log('Call')
            await forceDispatch({ wait: 0.25, timeout: 1 }, ELEMENTS["Call"], 'click')
            await forceDispatch({ wait: 0.25, timeout: 1 }, ELEMENTS["Call"], 'mousedown')
            await forceDispatch({ wait: 0.25, timeout: 1 }, ELEMENTS["Call"], 'mouseup')
            console.log('Calling')
            await waitFor({ wait: 0.25, timeout: 1 }, ELEMENTS["Calling"])
            console.log('Mute')
            await forceDispatch({ wait: 0.5, timeout: 1 }, ELEMENTS["Mute"](name), 'click')
            
            branch(
                {
                    timeout: 30,
                    timeoutCallback: async () => {
                        console.log('Timeout happened');
                        resolve();
                    },
                },

                ['appear', ELEMENTS["Number was unreachable"](name), () => {
                    console.log('Number was unreachable');
                    resolve();
                }],

                ['appear', ELEMENTS["Number was busy"](name), () => {
                    console.log('Number was busy');
                    resolve();
                }],

                ['appear', ELEMENTS["In voice call"](name), async function() {
                    console.log('In voice call');
                    branch(
                        {
                            timeout: 30,
                            timeoutCallback: async () => {
                                console.log('Timeout happened');
                                resolve();
                            },
                        },
                                
                        ['appear', ELEMENTS["Number was unreachable"](name), () => {
                            console.log('Number was unreachable');
                            resolve();
                        }],

                        ['appear', ELEMENTS["Number was busy"](name), () => {
                            console.log('Number was busy');
                            resolve();
                        }],

                        ['disappear', ELEMENTS["In voice call"](name), () => {
                            console.log('Voicecall ended');
                            resolve();
                        }],

                        ['disappear', () => DOCUMENTS["Dialer"](name), () => {
                            console.log('Dialer is closed');
                            resolve();
                        }],
                    )
                }],

                ['disappear', () => DOCUMENTS["Dialer"](name), () => {
                    console.log('Dialer is closed');
                    resolve();
                }],
            )
        } catch (err) {
            console.error(err);
            resolve();
        }
    })
}

async function main() {
    while (true) {
        await call("+1", "8558527658", "Unknown");
        try {
            const hangup = ELEMENTS["Hang up"]("Unknown")();
            if (hangup) {
                console.log('Hanging up');
                dispatch(hangup, 'click');
            }
        } catch (err) {
            console.error(err);
        }
        try {
            const close = ELEMENTS["Close"]("Unknown")();
            if (close) {
                console.log('Closing dialer');
                dispatch(close, 'click');
            }
        } catch (err) {
            console.error(err);
        }
    }
}

window.addEventListener('load', main);
if (document.readyState === "complete") main();

#8

Using the latest version of Chrome:

  1. Open hangouts.google.com, sign in, you should be seeing this:

  1. Open Developer Tools by clicking the three dots in the top right corner, More Tools, Developer Tools

  2. Click on the Console tab

  3. Paste in the snippet of code above and run it.

WARNING: You should not be running code in your browser written by people you don’t trust. It can be very harmful and dangerous! Proceed at your own risk. Read the code I’ve written and be confident that it does not contain anything malicious before proceeding. You have my word that I am not doing anything outside of what I said the script will do.

  1. You should be seeing hangouts automatically dial the number that is hardcoded in the code.

Changing the number

Find the line await call("+1", "8558527658", "Unknown"); and change to the number you want to call.

Modification

I’ve designed the code to be easy to read and work with, feel free to ask me to explain parts of it that you don’t understand.