Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Browser userscripts to delete all Google Voice history
// ==UserScript==
// @name Delete Google Voice (Legacy) History
// @description Deletes all Google Voice (Legacy) history
// @version 1.1.0
// @license MIT
// @namespace gavinhungry.io
// @author Gavin Lloyd <gavinhungry@gmail.com>
//
// @include https://www.google.tld/voice
// @include https://www.google.tld/voice/*
// @include https://www.google.tld/voice#*
// @include https://www.google.tld/voice?*
//
// @require https://cdn.jsdelivr.net/jquery/2/jquery.min.js
// @grant none
// ==/UserScript==
(function($) {
'use strict';
var voice = (function() {
var GV_KEY = '_rnr_se';
/**
* Remove a message id
*
* @param {String} url
* @param {String} message - message id
* @return {Promise}
*/
var remove = function(url, message) {
var d = $.Deferred();
var data = {
messages: message,
trash: 1
};
data[GV_KEY] = $('input[name="'+ GV_KEY +'"]').first().val();
$.ajax({
type: 'POST',
url: url,
data: data
}).always(d.resolve);
return d.promise();
};
/**
* Remove a collection of message ids
*
* @param {String} url
* @param {Array} messages - message ids
* @return {Promise}
*/
var batch = function(url, messages) {
var d = $.Deferred();
var complete = 0;
var pending = messages.map(function(message) {
return remove(url, message).then(function() {
d.notify(++complete);
});
});
$.when.apply(null, pending).then(d.resolve);
return d.promise();
};
return {
/**
* Get all message ids from a URL
*
* @param {String} url
* @return {Promise}
*/
get: function(url) {
var page = 1;
var messages = [];
return (function next() {
return $.ajax({
url: url,
data: { page: 'p' + page++ }
}).then(function(data) {
var json = $(data).find('json').text();
var results = Object.keys(JSON.parse(json).messages);
messages = messages.concat(results);
return results.length ? next() : messages;
}, function() {
return messages;
});
})();
},
/**
* Trash a collection of messages
*
* @param {Promise|Array} messages
* @return {Promise}
*/
trash: function(messages) {
return $.when(messages).then(function(messages) {
return batch('/voice/inbox/deleteMessages/', messages);
});
},
/**
* Delete a collection of messages forever
*
* @param {Promise|Array} messages
* @return {Promise}
*/
forever: function(messages) {
return $.when(messages).then(function(messages) {
return batch('/voice/inbox/deleteForeverMessages/', messages);
});
}
};
})();
var _handler = function(url, method) {
return function(e) {
if (!confirm('Are you sure?')) { return; }
$(this).addClass('jfk-button-disabled').off('click');
var $span = $(this).find('span').text('(...)');
voice.get(url).done(function(messages) {
method(messages).progress(function(complete) {
$span.text('(' + complete + '/' + messages.length + ')');
}).done(function() {
window.location.reload();
});
});
};
};
var $history = $('<div class="goog-inline-block jfk-button jfk-button-standard jfk-button-collapse-right" style="margin-left: 16px;">Delete ALL <span></span></div>');
var $empty = $('<div class="goog-inline-block jfk-button jfk-button-standard jfk-button-collapse-left">Empty Trash <span></span></div>');
$history.on('click', _handler('/voice/b/0/inbox/recent/all', voice.trash));
$empty.on('click', _handler('/voice/inbox/recent/trash/', voice.forever));
var i = setInterval(function() {
var $parent = $('.gc-appbar-buttons-left');
if ($parent.length) {
clearInterval(i);
$parent.append($history, $empty);
}
}, 5);
})(jQuery);
// ==UserScript==
// @name Delete Google Voice History (WORK IN PROGRESS)
// @description Deletes all Google Voice history
// @version 2.0.0
// @license MIT
// @namespace gavinhungry.io
// @author Gavin Lloyd <gavinhungry@gmail.com>
//
// @include https://voice.google.tld/*
//
// @require https://cdn.jsdelivr.net/jquery/2/jquery.min.js
// @grant none
// ==/UserScript==
(function($) {
'use strict';
// allow us to examine Angular scopes - will refresh the page
if (!angular.element(document.body).scope()) {
angular.reloadWithDebugInfo();
}
// Adds hidden "Delete" option for entire thread - does not seem to work yet
_gv.soyProto.ClientFlags[11].push('deleteThread');
var deleteMessage = function(message) {
console.log('Deleting message');
message.dispatchEvent(new Event('contextmenu'));
return new Promise(function(res, rej) {
setTimeout(function() {
// click the delete button
$('.md-open-menu-container button[aria-label=Delete]:visible').trigger('click');
setTimeout(function() {
// this probably is not necessary - we just need to click the damned button
var $dialogActions = $('button[gv-test-id="message-delete-ok"]').closest('md-dialog-actions');
angular.element($dialogActions).scope().ctrl.onConfirmClick();
setTimeout(res, 250);
}, 250);
}, 250); // FIXME: we are waiting for the context menu to open
});
};
var deleteMessages = function(messages) {
var message = messages.shift();
if (!message) {
return Promise.resolve();
}
deleteMessage(message).then(function() {
deleteMessages(messages);
});
};
$(function() {
var $voicemailButton = $('gv-mini-nav gv-nav-button[icon=voicemail]');
var $deleteButton = $('gv-mini-nav gv-nav-button [role=button][aria-selected!=true]').first().clone();
$deleteButton.find('md-icon').html('delete');
$deleteButton.css('user-select', 'none');
$deleteButton.insertAfter($voicemailButton);
// FIXME: This sucks. Uses the UI, is slow and unreliable and only works on the active thread.
$deleteButton.on('click', function() {
if (!confirm('Are you sure?')) {
return;
}
var messages = $('gv-message-item [gv-test-id]').toArray();
console.log('Deleting', messages.length, 'messages ...');
deleteMessages(messages).then(function() {
console.log('Done deleting messages!');
});
});
});
})(jQuery);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.